diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 3a5bbd9f6..e5a68542f 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -11,6 +11,6 @@ on: jobs: changelog: uses: ansible-network/github_actions/.github/workflows/changelog.yml@main - if: (github.event_name != 'schedule' && github.event_name != 'push' && !contains(github.event.pull_request.labels.*.name, 'new_resource_module')) + if: (github.event_name != 'schedule' && github.event_name != 'push' && !contains(github.event.pull_request.labels.*.name, 'new_resource_module') && !contains(github.event.pull_request.labels.*.name, 'documentation')) sanity: uses: ansible-network/github_actions/.github/workflows/sanity.yml@main diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d3ecd6f70..26faff455 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,9 +1,125 @@ -====================================== -Dellemc.Enterprise_Sonic Release Notes -====================================== +======================================= +Dellemc.Enterprise\_Sonic Release Notes +======================================= .. contents:: Topics +v2.5.0 +====== + +Release Summary +--------------- + +| Release Date: 2024-0812 +| +| This release provides enhanced Dell Enterprise SONiC Ansible Collection support for SONiC 4.x images. +| In addition to new resource modules to support previously existing functionality, it provides +| support for several new features released in SONiC releases 4.1, 4.2, and 4.4. +| It also provides bug fixes and enhancements for support of features that were initially introduced +| in previous Enterprise SONiC Ansible releases. The changelog describes changes made to the modules +| included in this collection since release 2.0.0. +| +| Additional details are described below. +| 1) Update the "requires_ansible" version in the meta/runtime.yml file for this collection +| to the oldest supported version of ansible-core. (This was recently changed by Redhat/Ansible +| to version "2.15.0".) +| 2) Update the list of resource modules in the README file to include all currently available +| resource modules for this collection. + +Minor Changes +------------- + +- bgp_af - Add support for 'import vrf' commands (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/351). +- sonic_bfd - Add playbook check and diff modes support for bfd module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). +- sonic_bgp - Add playbook check and diff modes support for bgp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). +- sonic_bgp - Add support BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). +- sonic_bgp - Fix GitHub issue# 416 (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/418). +- sonic_bgp_af - Add playbook check and diff modes support for bgp_af module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). +- sonic_bgp_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). +- sonic_bgp_af - Add support for aggregate address configuration(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/398). +- sonic_bgp_af - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/400) +- sonic_bgp_as_paths - Add playbook check and diff modes support for bgp_as_paths module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). +- sonic_bgp_communities - Add playbook check and diff modes support for bgp_communities module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). +- sonic_bgp_ext_communities - Add playbook check and diff modes support for bgp_ext_communities module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). +- sonic_bgp_neighbors - Add playbook check and diff modes support for bgp_neighbors module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/360). +- sonic_bgp_neighbors - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). +- sonic_bgp_neighbors - Add support for replaced and overridden states (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). +- sonic_bgp_neighbors - Add support for replaced and overridden states (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/336). +- sonic_bgp_neighbors - Add support for the "fabric_external" option (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/336). +- sonic_bgp_neighbors_af - Add playbook check and diff modes support for bgp_neighbors_af module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/360). +- sonic_bgp_neighbors_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). +- sonic_copp - Add playbook check and diff modes support for copp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). +- sonic_dhcp_relay - Add playbook check and diff modes support for dhcp_relay module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). +- sonic_dhcp_snooping - Add playbook check and diff modes support for dhcp_snooping module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). +- sonic_interfaces - Add description, enabled option support for Loopback interfaces (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). +- sonic_interfaces - Fix GitHub issue 357 - set proper default value when deleted (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/366). +- sonic_interfaces - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). +- sonic_l3_interfaces - Add playbook check and diff modes support for l3_interfaces module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/328). +- sonic_l3_interfaces - Add support for USGv6R1 related features (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/374). +- sonic_l3_interfaces - Fix IPv6 default dad configuration handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/428). +- sonic_lag_interfaces - Add evpn ethernet-segment support for LAG interfaces (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/403). +- sonic_lldp_global - Add playbook check and diff modes support for lldp_global module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). +- sonic_logging - Add support for protocol option in logging module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/317). +- sonic_mac - Add playbook check and diff modes support for mac module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). +- sonic_mclag - Add playbook check and diff modes support for mclag module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). +- sonic_mclag - Enable session-vrf command support in mclag(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/299). +- sonic_port_breakout - Add playbook check and diff modes support for port_breakout module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). +- sonic_port_group - Make error message for port group facts gathering more descriptive (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/396). +- sonic_prefix_lists - Add playbook check and diff modes support for prefix_lists module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). +- sonic_qos_maps - Comment out PFC priority group map tests cases (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/395). +- sonic_qos_scheduler - Update states implementation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/373). +- sonic_route_maps - Add UT for route maps module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/384). +- sonic_route_maps - Add playbook check and diff modes support for route_maps module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). +- sonic_route_maps - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). +- sonic_route_maps - Add support for the 'set tag' option and synchronize module documentation with argspec and model (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/413). +- sonic_stp - Add playbook check and diff modes support for stp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). +- sonic_system - Add support for 'standard_extended' interface-naming mode (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/352). +- sonic_system - Add support for configuring auto-breakout feature (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/342). +- sonic_system - Adding Versatile Hash feature.(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/401). +- sonic_system - Enable auditd command support(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/405). +- sonic_system - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/388). +- sonic_vxlan - Fix GitHub issue 376 - Change vxlan module get_fact function (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/393). +- sonic_vxlans - Add playbook check and diff modes support for vxlans module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). +- sonic_vxlans - Add support for the "external_ip" vxlan option (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/330). + +Bugfixes +-------- + +- sonic_bfd - Fix BFD states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). +- sonic_bgp_neighbors - Fix issues with deleted state (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). +- sonic_copp - Fix CoPP states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/381). +- sonic_interfaces - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/377). +- sonic_interfaces - Fix replaced and overridden state handling for Loopback interfaces (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). +- sonic_l2_interfaces - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/410). +- sonic_l3_interfaces - Fix replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/431). +- sonic_mac - Fix MAC states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). +- sonic_prefix_lists - Fix idempotency failure (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/354). +- sonic_prefix_lists - Fix replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/354). +- sonic_qos_pfc - Add back accidentally deleted line of code (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/391). +- sonic_static_routes - Fix static routes states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). +- sonic_vlans - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/377). + +New Modules +----------- + +- dellemc.enterprise_sonic.sonic_ldap - Configure global LDAP server settings on SONiC. +- dellemc.enterprise_sonic.sonic_login_lockout - Manage Global Login Lockout configurations on SONiC. +- dellemc.enterprise_sonic.sonic_mgmt_servers - Manage management servers configuration on SONiC. +- dellemc.enterprise_sonic.sonic_ospf_area - configure OSPF area settings on SONiC. +- dellemc.enterprise_sonic.sonic_ospfv2 - Configure global OSPFv2 protocol settings on SONiC. +- dellemc.enterprise_sonic.sonic_ospfv2_interfaces - Configure OSPFv2 interface mode protocol settings on SONiC. +- dellemc.enterprise_sonic.sonic_pim_global - Manage global PIM configurations on SONiC. +- dellemc.enterprise_sonic.sonic_pim_interfaces - Manage interface-specific PIM configurations on SONiC. +- dellemc.enterprise_sonic.sonic_poe - Manage PoE configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_buffer - Manage QoS buffer configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_interfaces - Manage QoS interfaces configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_maps - Manage QoS maps configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_pfc - Manage QoS PFC configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_scheduler - Manage QoS scheduler configuration on SONiC. +- dellemc.enterprise_sonic.sonic_qos_wred - Manage QoS WRED profiles configuration on SONiC. +- dellemc.enterprise_sonic.sonic_roce - Manage RoCE QoS configuration on SONiC. +- dellemc.enterprise_sonic.sonic_sflow - configure sflow settings on SONiC. +- dellemc.enterprise_sonic.sonic_vrrp - Configure VRRP protocol settings on SONiC. v2.4.0 ====== @@ -26,7 +142,6 @@ Release Summary | 3) Update the list of resource modules in the README file to include all currently available | resource modules for this collection. - Bugfixes -------- @@ -55,7 +170,6 @@ Release Summary | Please refer to the "CHANGELOG.rst" file at the top directory level of this repo for additional | details on the contents of this release. - Minor Changes ------------- @@ -149,7 +263,6 @@ Release Summary | version and the revised "galaxy.yml" file for this release enables installation of these | newer versions. - Minor Changes ------------- @@ -170,7 +283,6 @@ Release Summary | Enterprise SONiC Ansible releases. The changelog describes changes made to the modules and plugins | included in this collection since release 2.0.0. - Minor Changes ------------- @@ -292,7 +404,6 @@ Release Summary This release provides Dell SONiC Enterprise Ansible Collection support for SONiC 4.x images. It is the first release for the 2.x branch of the collection. Subsequent enhancements for support of SONiC 4.x images will also be provided as needed on the 2.x branch. This release also contains bugfixes and enhancements to supplement the Ansible functionality provided previously for SONiC 3.x images. The changelog describes changes made to the modules and plugins included in this collection since release 1.1.0. - Minor Changes ------------- diff --git a/README.md b/README.md index d4a698d73..01bdda0f0 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ Name | Description [**sonic_copp**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_copp_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-copp-module)| Manage CoPP configuration [**sonic_dhcp_relay**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_dhcp_relay_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-dhcp-relay-module)| Manage DHCP and DHCPv6 relay configurations [**sonic_dhcp_snooping**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_dhcp_snooping_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-dhcp-snooping-module)| Manage DHCP Snooping +[**sonic_fips**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_fips_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-fips-module)| Manage FIPS configurations +[**sonic_image_management**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_image_management_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-image-management-module)| Manage installation of Enterprise SONiC image, software patch and firmware updater. [**sonic_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-interfaces-module)| Configure Interface attributes [**sonic_ip_neighbor**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_sonic_ip_neighbor_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sonic-ip-neighbor-module)| Manage IP neighbor global configuration [**sonic_l2_acls**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_l2_acls_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sonic-l2-acls-module)| Manage Layer 2 access control lists (ACL) configurations @@ -55,17 +57,35 @@ Name | Description [**sonic_l3_acls**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_l3_acls_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sonic-l3-acls-module)| Manage Layer 3 access control lists (ACL) configurations [**sonic_l3_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_l3_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-l3-interfaces-module)| Configure the IPv4 and IPv6 parameters on Interfaces [**sonic_lag_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_lag_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-lag-interfaces-module)| Manage link aggregation group (LAG) interface parameters +[**sonic_ldap**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_ldap_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-ldap-module)| Configure global LDAP server settings [**sonic_lldp_global**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_sonic_lldp_global_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sonic-lldp-global-module)| Manage Global LLDP configurations +[**sonic_lldp_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_sonic_lldp_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sonic-lldp-interfaces-module)| Manage interface LLDP configurations [**sonic_logging**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_logging_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-logging-module)| Manage logging configuration +[**sonic_login_lockout**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_login_lockout_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-login-lockout-module)| Manage Global Login Lockout configuration [**sonic_mac**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_mac_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-mac-module)| Manage MAC configuration [**sonic_mclag**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_mclag_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-mclag-module)| Manage multi chassis link aggregation groups domain (MCLAG) and its parameters +[**sonic_mgmt_servers**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_mgmt_servers_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-mgmt-servers-module)| Manage management servers configuration [**sonic_ntp**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_ntp_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-ntp-module)| Manage NTP configuration +[**sonic_ospf_area**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_ospf_area_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-ospf-area-module)| Configure OSPF area setting +[**sonic_ospfv2**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_ospfv2_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-ospfv2-module)| Configure global OSPFv2 protocol settings +[**sonic_ospfv2_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_ospfv2_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-ospfv2-interfaces-module)| Configure OSPFv2 interface mode protocol settings +[**sonic_pim_global**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_pim_global_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-pim-global-module)| Manage global PIM configuration +[**sonic_pim_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_pim_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-pim-interfaces-module)| Manage interface-specific PIM configurations [**sonic_pki**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_pki_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-pki-module)| Manages PKI attributes +[**sonic_poe**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_poe_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-poe-module)| Manage Power over Ethernet PoE configuration [**sonic_port_breakout**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_port_breakout_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-port-breakout-module)| Configure port breakout settings on physical interfaces [**sonic_port_group**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_port_group_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-port-group-module)| Manage port group configuration [**sonic_prefix_lists**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_prefix_lists_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-prefix-lists-module)| Manage prefix list configuration +[**sonic_qos_buffer**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_buffer_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos-buffer-module)| Manage QoS buffer configuration +[**sonic_qos_interfaces**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_interfaces_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos_interfaces-module)| Manage QoS interfaces configuration +[**sonic_qos_maps**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_maps_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos_maps-module)| Manage QoS maps configuration +[**sonic_qos_pfc**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_pfc_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos-pfc-module)| Manage QoS PFC configuration +[**sonic_qos_scheduler**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_scheduler_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos-scheduler-module)| Manage QoS scheduler configuration +[**sonic_qos_wred**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_qos_wred_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-qos-scheduler-module)| Manage QoS WRED profiles configuration [**sonic_radius_server**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_radius_server_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-radius-server-module)| Manage RADIUS server and its parameters +[**sonic_roce**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_roce_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-roce-module)| Manage RoCE QoS configuration [**sonic_route_maps**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_route_maps_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-route-maps-module)| Manage route map configuration +[**sonic_sflow**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_sflow_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-sflow-module)| Manage sflow configuration settings [**sonic_static_routes**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_static_routes_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-static-routes-module)| Manage static routes configuration [**sonic_stp**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_stp_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-stp-module)| Manage STP configuration [**sonic_system**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_system_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-system-module)| Configure system parameters @@ -74,6 +94,7 @@ Name | Description [**sonic_vlan_mapping**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_vlan_mapping_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-vlan-mapping-module)| Configure vlan mappings [**sonic_vlans**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_vlans_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-vlans-module)| Manage VLAN and its parameters [**sonic_vrfs**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_vrfs_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-vrfs-module)| Manage VRFs and associate VRFs to interfaces +[**sonic_vrrp**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_vrrp_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-vrrp-module)| Manage VRRP protocol configuration settings [**sonic_vxlans**](https://docs.ansible.com/ansible/latest/collections/dellemc/enterprise_sonic/sonic_vxlans_module.html#ansible-collections-dellemc-enterprise-sonic-sonic-vxlans-module)| Manage VxLAN EVPN and its parameters Sample use case playbooks @@ -283,4 +304,16 @@ Code of Conduct This repository adheres to the [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) +Communication +------------- + +* Join the Ansible forum: + * [Get Help](https://forum.ansible.com/c/help/6): get help or help others. + * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. + * [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events. + +* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes. + +For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). + (c) 2020-2021 Dell Inc. or its subsidiaries. All Rights Reserved. diff --git a/changelogs/.plugin-cache.yaml b/changelogs/.plugin-cache.yaml index 8b0d92009..247656f3c 100644 --- a/changelogs/.plugin-cache.yaml +++ b/changelogs/.plugin-cache.yaml @@ -6,7 +6,7 @@ plugins: callback: {} cliconf: sonic: - description: Use sonic cliconf to run command on Dell OS10 platform + description: Use sonic cliconf to run command on Dell SONiC platforms name: sonic version_added: null connection: {} @@ -104,6 +104,17 @@ plugins: name: sonic_facts namespace: '' version_added: 1.0.0 + sonic_fips: + description: Manage FIPS configurations on SONiC + name: sonic_fips + namespace: '' + version_added: 2.1.0 + sonic_image_management: + description: Manage installation of Enterprise SONiC image, software patch and + firmware updater + name: sonic_image_management + namespace: '' + version_added: 2.4.0 sonic_interfaces: description: Configure Interface attributes on interfaces such as, Eth, LAG, VLAN, and loopback. (create a loopback interface if it does not exist.) @@ -142,16 +153,31 @@ plugins: name: sonic_lag_interfaces namespace: '' version_added: 1.0.0 + sonic_ldap: + description: Configure global LDAP server settings on SONiC. + name: sonic_ldap + namespace: '' + version_added: 2.5.0 sonic_lldp_global: description: Manage Global LLDP configurations on SONiC name: sonic_lldp_global namespace: '' version_added: 2.1.0 + sonic_lldp_interfaces: + description: Manage Inteface LLDP configurations on SONiC + name: sonic_lldp_interfaces + namespace: '' + version_added: 2.1.0 sonic_logging: description: Manage logging configuration on SONiC. name: sonic_logging namespace: '' version_added: 2.1.0 + sonic_login_lockout: + description: Manage Global Login Lockout configurations on SONiC + name: sonic_login_lockout + namespace: '' + version_added: 2.5.0 sonic_mac: description: Manage MAC configuration on SONiC name: sonic_mac @@ -163,16 +189,51 @@ plugins: name: sonic_mclag namespace: '' version_added: 1.0.0 + sonic_mgmt_servers: + description: Manage management servers configuration on SONiC + name: sonic_mgmt_servers + namespace: '' + version_added: 2.5.0 sonic_ntp: description: Manage NTP configuration on SONiC. name: sonic_ntp namespace: '' version_added: 2.0.0 + sonic_ospf_area: + description: configure OSPF area settings on SONiC + name: sonic_ospf_area + namespace: '' + version_added: 2.5.0 + sonic_ospfv2: + description: Configure global OSPFv2 protocol settings on SONiC. + name: sonic_ospfv2 + namespace: '' + version_added: 2.5.0 + sonic_ospfv2_interfaces: + description: Configure OSPFv2 interface mode protocol settings on SONiC. + name: sonic_ospfv2_interfaces + namespace: '' + version_added: 2.5.0 + sonic_pim_global: + description: Manage global PIM configurations on SONiC + name: sonic_pim_global + namespace: '' + version_added: 2.5.0 + sonic_pim_interfaces: + description: Manage interface-specific PIM configurations on SONiC + name: sonic_pim_interfaces + namespace: '' + version_added: 2.5.0 sonic_pki: description: Manages PKI attributes of Enterprise Sonic name: sonic_pki namespace: '' version_added: 2.3.0 + sonic_poe: + description: Manage PoE configuration on SONiC + name: sonic_poe + namespace: '' + version_added: 2.5.0 sonic_port_breakout: description: Configure port breakout settings on physical interfaces name: sonic_port_breakout @@ -188,16 +249,56 @@ plugins: name: sonic_prefix_lists namespace: '' version_added: 2.0.0 + sonic_qos_buffer: + description: Manage QoS buffer configuration on SONiC + name: sonic_qos_buffer + namespace: '' + version_added: 2.5.0 + sonic_qos_interfaces: + description: Manage QoS interfaces configuration on SONiC + name: sonic_qos_interfaces + namespace: '' + version_added: 2.5.0 + sonic_qos_maps: + description: Manage QoS maps configuration on SONiC + name: sonic_qos_maps + namespace: '' + version_added: 2.5.0 + sonic_qos_pfc: + description: Manage QoS PFC configuration on SONiC + name: sonic_qos_pfc + namespace: '' + version_added: 2.5.0 + sonic_qos_scheduler: + description: Manage QoS scheduler configuration on SONiC + name: sonic_qos_scheduler + namespace: '' + version_added: 2.5.0 + sonic_qos_wred: + description: Manage QoS WRED profiles configuration on SONiC + name: sonic_qos_wred + namespace: '' + version_added: 2.5.0 sonic_radius_server: description: Manage RADIUS server and its parameters name: sonic_radius_server namespace: '' version_added: 1.0.0 + sonic_roce: + description: Manage RoCE QoS configuration on SONiC + name: sonic_roce + namespace: '' + version_added: 2.5.0 sonic_route_maps: description: route map configuration handling for SONiC name: sonic_route_maps namespace: '' version_added: 2.1.0 + sonic_sflow: + description: configure sflow settings on SONiC + name: sonic_sflow + namespace: '' + version_added: 2.5.0 sonic_static_routes: description: Manage static routes configuration on SONiC name: sonic_static_routes @@ -239,6 +340,11 @@ plugins: name: sonic_vrfs namespace: '' version_added: 1.0.0 + sonic_vrrp: + description: Configure VRRP protocol settings on SONiC. + name: sonic_vrrp + namespace: '' + version_added: 2.5.0 sonic_vxlans: description: Manage VxLAN EVPN and its parameters name: sonic_vxlans @@ -249,4 +355,4 @@ plugins: strategy: {} test: {} vars: {} -version: 2.4.0 +version: 2.5.0 diff --git a/changelogs/fragments/299-mclag-session-vrf-support.yaml b/changelogs/archive_fragments/2.5.0/299-mclag-session-vrf-support.yaml similarity index 100% rename from changelogs/fragments/299-mclag-session-vrf-support.yaml rename to changelogs/archive_fragments/2.5.0/299-mclag-session-vrf-support.yaml diff --git a/changelogs/fragments/317-added-protocol-option-for-syslog-module.yaml b/changelogs/archive_fragments/2.5.0/317-added-protocol-option-for-syslog-module.yaml similarity index 100% rename from changelogs/fragments/317-added-protocol-option-for-syslog-module.yaml rename to changelogs/archive_fragments/2.5.0/317-added-protocol-option-for-syslog-module.yaml diff --git a/changelogs/fragments/328-playbook-check-diff-modes-for-l3-interfaces.yaml b/changelogs/archive_fragments/2.5.0/328-playbook-check-diff-modes-for-l3-interfaces.yaml similarity index 100% rename from changelogs/fragments/328-playbook-check-diff-modes-for-l3-interfaces.yaml rename to changelogs/archive_fragments/2.5.0/328-playbook-check-diff-modes-for-l3-interfaces.yaml diff --git a/changelogs/fragments/329-regression-test-automated-intf-naming-config.yaml b/changelogs/archive_fragments/2.5.0/329-regression-test-automated-intf-naming-config.yaml similarity index 100% rename from changelogs/fragments/329-regression-test-automated-intf-naming-config.yaml rename to changelogs/archive_fragments/2.5.0/329-regression-test-automated-intf-naming-config.yaml diff --git a/changelogs/fragments/330-add-external-ip-attribute-for-vxlan.yaml b/changelogs/archive_fragments/2.5.0/330-add-external-ip-attribute-for-vxlan.yaml similarity index 100% rename from changelogs/fragments/330-add-external-ip-attribute-for-vxlan.yaml rename to changelogs/archive_fragments/2.5.0/330-add-external-ip-attribute-for-vxlan.yaml diff --git a/changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml b/changelogs/archive_fragments/2.5.0/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml similarity index 100% rename from changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml rename to changelogs/archive_fragments/2.5.0/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml diff --git a/changelogs/archive_fragments/2.5.0/335-replaced-overridden-for-bgp-neighbors-module.yaml b/changelogs/archive_fragments/2.5.0/335-replaced-overridden-for-bgp-neighbors-module.yaml new file mode 100644 index 000000000..00d9b02c6 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/335-replaced-overridden-for-bgp-neighbors-module.yaml @@ -0,0 +1,5 @@ +minor_changes: + - sonic_bgp_neighbors - Add support for replaced and overridden states (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). + +bugfixes: + - sonic_bgp_neighbors - Fix issues with deleted state (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). diff --git a/changelogs/fragments/336-replaced-overridden-for-bgp-neighbors-af-module.yaml b/changelogs/archive_fragments/2.5.0/336-replaced-overridden-for-bgp-neighbors-af-module.yaml similarity index 100% rename from changelogs/fragments/336-replaced-overridden-for-bgp-neighbors-af-module.yaml rename to changelogs/archive_fragments/2.5.0/336-replaced-overridden-for-bgp-neighbors-af-module.yaml diff --git a/changelogs/fragments/337-playbook-check-diff-modes-for-mclag-port-breakout-vxlans.yaml b/changelogs/archive_fragments/2.5.0/337-playbook-check-diff-modes-for-mclag-port-breakout-vxlans.yaml similarity index 100% rename from changelogs/fragments/337-playbook-check-diff-modes-for-mclag-port-breakout-vxlans.yaml rename to changelogs/archive_fragments/2.5.0/337-playbook-check-diff-modes-for-mclag-port-breakout-vxlans.yaml diff --git a/changelogs/fragments/338-playbook-check-diff-modes-for-mac-stp-lldp.yaml b/changelogs/archive_fragments/2.5.0/338-playbook-check-diff-modes-for-mac-stp-lldp.yaml similarity index 100% rename from changelogs/fragments/338-playbook-check-diff-modes-for-mac-stp-lldp.yaml rename to changelogs/archive_fragments/2.5.0/338-playbook-check-diff-modes-for-mac-stp-lldp.yaml diff --git a/changelogs/fragments/342-system-auto-breakout-module.yaml b/changelogs/archive_fragments/2.5.0/342-system-auto-breakout-module.yaml similarity index 100% rename from changelogs/fragments/342-system-auto-breakout-module.yaml rename to changelogs/archive_fragments/2.5.0/342-system-auto-breakout-module.yaml diff --git a/changelogs/fragments/344-update-ut-config-requests-validation.yaml b/changelogs/archive_fragments/2.5.0/344-update-ut-config-requests-validation.yaml similarity index 100% rename from changelogs/fragments/344-update-ut-config-requests-validation.yaml rename to changelogs/archive_fragments/2.5.0/344-update-ut-config-requests-validation.yaml diff --git a/changelogs/fragments/346-playbook-check-diff-modes-for-bfd-copp-dhcps.yaml b/changelogs/archive_fragments/2.5.0/346-playbook-check-diff-modes-for-bfd-copp-dhcps.yaml similarity index 100% rename from changelogs/fragments/346-playbook-check-diff-modes-for-bfd-copp-dhcps.yaml rename to changelogs/archive_fragments/2.5.0/346-playbook-check-diff-modes-for-bfd-copp-dhcps.yaml diff --git a/changelogs/fragments/350-playbook-check-diff-modes-for-bgp-modules.yaml b/changelogs/archive_fragments/2.5.0/350-playbook-check-diff-modes-for-bgp-modules.yaml similarity index 100% rename from changelogs/fragments/350-playbook-check-diff-modes-for-bgp-modules.yaml rename to changelogs/archive_fragments/2.5.0/350-playbook-check-diff-modes-for-bgp-modules.yaml diff --git a/changelogs/fragments/351-bgp-af-import-vrf-support.yaml b/changelogs/archive_fragments/2.5.0/351-bgp-af-import-vrf-support.yaml similarity index 100% rename from changelogs/fragments/351-bgp-af-import-vrf-support.yaml rename to changelogs/archive_fragments/2.5.0/351-bgp-af-import-vrf-support.yaml diff --git a/changelogs/fragments/352-system-standard-extended-interface-naming.yaml b/changelogs/archive_fragments/2.5.0/352-system-standard-extended-interface-naming.yaml similarity index 100% rename from changelogs/fragments/352-system-standard-extended-interface-naming.yaml rename to changelogs/archive_fragments/2.5.0/352-system-standard-extended-interface-naming.yaml diff --git a/changelogs/fragments/354-prefix-lists-fix-replaced-idempotency.yaml b/changelogs/archive_fragments/2.5.0/354-prefix-lists-fix-replaced-idempotency.yaml similarity index 100% rename from changelogs/fragments/354-prefix-lists-fix-replaced-idempotency.yaml rename to changelogs/archive_fragments/2.5.0/354-prefix-lists-fix-replaced-idempotency.yaml diff --git a/changelogs/fragments/360-playbook-check-diff-modes-for-bgp-neighbors-and-af.yaml b/changelogs/archive_fragments/2.5.0/360-playbook-check-diff-modes-for-bgp-neighbors-and-af.yaml similarity index 100% rename from changelogs/fragments/360-playbook-check-diff-modes-for-bgp-neighbors-and-af.yaml rename to changelogs/archive_fragments/2.5.0/360-playbook-check-diff-modes-for-bgp-neighbors-and-af.yaml diff --git a/changelogs/fragments/364-interfaces-fix-loopback.yaml b/changelogs/archive_fragments/2.5.0/364-interfaces-fix-loopback.yaml similarity index 100% rename from changelogs/fragments/364-interfaces-fix-loopback.yaml rename to changelogs/archive_fragments/2.5.0/364-interfaces-fix-loopback.yaml diff --git a/changelogs/fragments/366-github-issue-357-fix.yaml b/changelogs/archive_fragments/2.5.0/366-github-issue-357-fix.yaml similarity index 100% rename from changelogs/fragments/366-github-issue-357-fix.yaml rename to changelogs/archive_fragments/2.5.0/366-github-issue-357-fix.yaml diff --git a/changelogs/fragments/373-qos-scheduler-update-states.yaml b/changelogs/archive_fragments/2.5.0/373-qos-scheduler-update-states.yaml similarity index 100% rename from changelogs/fragments/373-qos-scheduler-update-states.yaml rename to changelogs/archive_fragments/2.5.0/373-qos-scheduler-update-states.yaml diff --git a/changelogs/archive_fragments/2.5.0/374-usgv6r1-related-features-support.yaml b/changelogs/archive_fragments/2.5.0/374-usgv6r1-related-features-support.yaml new file mode 100644 index 000000000..6b7d1721e --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/374-usgv6r1-related-features-support.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_l3_interfaces - Add support for USGv6R1 related features (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/374). diff --git a/changelogs/fragments/377-interfaces-vlans-fix-facts.yaml b/changelogs/archive_fragments/2.5.0/377-interfaces-vlans-fix-facts.yaml similarity index 100% rename from changelogs/fragments/377-interfaces-vlans-fix-facts.yaml rename to changelogs/archive_fragments/2.5.0/377-interfaces-vlans-fix-facts.yaml diff --git a/changelogs/fragments/381-fix-copp-states.yaml b/changelogs/archive_fragments/2.5.0/381-fix-copp-states.yaml similarity index 100% rename from changelogs/fragments/381-fix-copp-states.yaml rename to changelogs/archive_fragments/2.5.0/381-fix-copp-states.yaml diff --git a/changelogs/fragments/383-fix-state-implementation.yaml b/changelogs/archive_fragments/2.5.0/383-fix-state-implementation.yaml similarity index 100% rename from changelogs/fragments/383-fix-state-implementation.yaml rename to changelogs/archive_fragments/2.5.0/383-fix-state-implementation.yaml diff --git a/changelogs/fragments/384-add-route-maps-ut.yaml b/changelogs/archive_fragments/2.5.0/384-add-route-maps-ut.yaml similarity index 100% rename from changelogs/fragments/384-add-route-maps-ut.yaml rename to changelogs/archive_fragments/2.5.0/384-add-route-maps-ut.yaml diff --git a/changelogs/archive_fragments/2.5.0/388-system-update-replaced-state.yaml b/changelogs/archive_fragments/2.5.0/388-system-update-replaced-state.yaml new file mode 100644 index 000000000..a0a6e05c4 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/388-system-update-replaced-state.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - sonic_system - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/388). diff --git a/changelogs/fragments/391-fix-qos-pfc-bug.yaml b/changelogs/archive_fragments/2.5.0/391-fix-qos-pfc-bug.yaml similarity index 100% rename from changelogs/fragments/391-fix-qos-pfc-bug.yaml rename to changelogs/archive_fragments/2.5.0/391-fix-qos-pfc-bug.yaml diff --git a/changelogs/archive_fragments/2.5.0/393-github-issue-376-fix.yaml b/changelogs/archive_fragments/2.5.0/393-github-issue-376-fix.yaml new file mode 100644 index 000000000..c10bc19b0 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/393-github-issue-376-fix.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_vxlan - Fix GitHub issue 376 - Change vxlan module get_fact function (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/393). diff --git a/changelogs/fragments/394-update-version-remove-facts-reports.yaml b/changelogs/archive_fragments/2.5.0/394-update-version-remove-facts-reports.yaml similarity index 100% rename from changelogs/fragments/394-update-version-remove-facts-reports.yaml rename to changelogs/archive_fragments/2.5.0/394-update-version-remove-facts-reports.yaml diff --git a/changelogs/fragments/395-comment-out-pfc-pg-test-cases.yaml b/changelogs/archive_fragments/2.5.0/395-comment-out-pfc-pg-test-cases.yaml similarity index 100% rename from changelogs/fragments/395-comment-out-pfc-pg-test-cases.yaml rename to changelogs/archive_fragments/2.5.0/395-comment-out-pfc-pg-test-cases.yaml diff --git a/changelogs/fragments/396-port-group-facts-enhancement.yaml b/changelogs/archive_fragments/2.5.0/396-port-group-facts-enhancement.yaml similarity index 100% rename from changelogs/fragments/396-port-group-facts-enhancement.yaml rename to changelogs/archive_fragments/2.5.0/396-port-group-facts-enhancement.yaml diff --git a/changelogs/archive_fragments/2.5.0/398-bgp-af-aggregate-address-support.yaml b/changelogs/archive_fragments/2.5.0/398-bgp-af-aggregate-address-support.yaml new file mode 100644 index 000000000..21fd5666b --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/398-bgp-af-aggregate-address-support.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_bgp_af - Add support for aggregate address configuration(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/398). diff --git a/changelogs/archive_fragments/2.5.0/400-bgp-af-update-replaced-state.yaml b/changelogs/archive_fragments/2.5.0/400-bgp-af-update-replaced-state.yaml new file mode 100644 index 000000000..9270c5fcc --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/400-bgp-af-update-replaced-state.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - sonic_bgp_af - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/400) diff --git a/changelogs/archive_fragments/2.5.0/401-versatile-hash.yaml b/changelogs/archive_fragments/2.5.0/401-versatile-hash.yaml new file mode 100644 index 000000000..2a8291b87 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/401-versatile-hash.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_system - Adding Versatile Hash feature.(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/401). diff --git a/changelogs/archive_fragments/2.5.0/403-lag-interface-evpn-ethernet-segment.yaml b/changelogs/archive_fragments/2.5.0/403-lag-interface-evpn-ethernet-segment.yaml new file mode 100644 index 000000000..d3fd0b904 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/403-lag-interface-evpn-ethernet-segment.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_lag_interfaces - Add evpn ethernet-segment support for LAG interfaces (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/403). diff --git a/changelogs/archive_fragments/2.5.0/405-auditd-config.yaml b/changelogs/archive_fragments/2.5.0/405-auditd-config.yaml new file mode 100644 index 000000000..a12da5308 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/405-auditd-config.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_system - Enable auditd command support(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/405). diff --git a/changelogs/archive_fragments/2.5.0/410-sonic_l2_interfaces-fix-facts-exception.yaml b/changelogs/archive_fragments/2.5.0/410-sonic_l2_interfaces-fix-facts-exception.yaml new file mode 100644 index 000000000..39f0c9e4e --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/410-sonic_l2_interfaces-fix-facts-exception.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - sonic_l2_interfaces - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/410). diff --git a/changelogs/archive_fragments/2.5.0/413-route-maps-set-tag-support-and-doc-updates.yaml b/changelogs/archive_fragments/2.5.0/413-route-maps-set-tag-support-and-doc-updates.yaml new file mode 100644 index 000000000..a9356adb3 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/413-route-maps-set-tag-support-and-doc-updates.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_route_maps - Add support for the 'set tag' option and synchronize module documentation with argspec and model (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/413). diff --git a/changelogs/archive_fragments/2.5.0/417-bgp-asn-notation.yaml b/changelogs/archive_fragments/2.5.0/417-bgp-asn-notation.yaml new file mode 100644 index 000000000..43741599c --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/417-bgp-asn-notation.yaml @@ -0,0 +1,6 @@ +minor_changes: + - sonic_bgp - Add support BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417) + - sonic_bgp_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417) + - sonic_bgp_neighbors - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417) + - sonic_bgp_neighbors_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417) + - sonic_route_maps - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417) diff --git a/changelogs/archive_fragments/2.5.0/418-github-issue-416-fix.yaml b/changelogs/archive_fragments/2.5.0/418-github-issue-416-fix.yaml new file mode 100644 index 000000000..d32085fec --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/418-github-issue-416-fix.yaml @@ -0,0 +1,2 @@ +minor_changes: + - sonic_bgp - Fix GitHub issue# 416 (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/418). diff --git a/changelogs/archive_fragments/2.5.0/422-update-ut-for-bgp-neighbors.yaml b/changelogs/archive_fragments/2.5.0/422-update-ut-for-bgp-neighbors.yaml new file mode 100644 index 000000000..ba43fbc38 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/422-update-ut-for-bgp-neighbors.yaml @@ -0,0 +1,3 @@ +--- +trivial: + - sonic_bgp_neighbors - Update UT for bgp_neighbors (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/422). diff --git a/changelogs/archive_fragments/2.5.0/428-l3-interfaces-default-dad-fix.yaml b/changelogs/archive_fragments/2.5.0/428-l3-interfaces-default-dad-fix.yaml new file mode 100644 index 000000000..909ebf2ed --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/428-l3-interfaces-default-dad-fix.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - sonic_l3_interfaces - Fix IPv6 default dad configuration handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/428). diff --git a/changelogs/archive_fragments/2.5.0/429-cliconf-doc-fix.yaml b/changelogs/archive_fragments/2.5.0/429-cliconf-doc-fix.yaml new file mode 100644 index 000000000..73ed9f868 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/429-cliconf-doc-fix.yaml @@ -0,0 +1,3 @@ +--- +trivial: + - cliconf - Change inherited OS10 references in description to SONiC (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/429). diff --git a/changelogs/archive_fragments/2.5.0/431-l3-interfaces-fix-replaced-state.yaml b/changelogs/archive_fragments/2.5.0/431-l3-interfaces-fix-replaced-state.yaml new file mode 100644 index 000000000..83c28ccdf --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/431-l3-interfaces-fix-replaced-state.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - sonic_l3_interfaces - Fix replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/431) diff --git a/changelogs/archive_fragments/2.5.0/432-changelogs-v2.5.0-release-note.yaml b/changelogs/archive_fragments/2.5.0/432-changelogs-v2.5.0-release-note.yaml new file mode 100644 index 000000000..69d67ff9d --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/432-changelogs-v2.5.0-release-note.yaml @@ -0,0 +1,3 @@ +--- +trivial: + - changelogs - Provide changelog updates for release 2.5.0 (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/432). diff --git a/changelogs/archive_fragments/2.5.0/v2.5.0_summary.yaml b/changelogs/archive_fragments/2.5.0/v2.5.0_summary.yaml new file mode 100644 index 000000000..5c6aa4326 --- /dev/null +++ b/changelogs/archive_fragments/2.5.0/v2.5.0_summary.yaml @@ -0,0 +1,16 @@ +release_summary: | + | Release Date: 2024-0812 + | + | This release provides enhanced Dell Enterprise SONiC Ansible Collection support for SONiC 4.x images. + | In addition to new resource modules to support previously existing functionality, it provides + | support for several new features released in SONiC releases 4.1, 4.2, and 4.4. + | It also provides bug fixes and enhancements for support of features that were initially introduced + | in previous Enterprise SONiC Ansible releases. The changelog describes changes made to the modules + | included in this collection since release 2.0.0. + | + | Additional details are described below. + | 1) Update the "requires_ansible" version in the meta/runtime.yml file for this collection + | to the oldest supported version of ansible-core. (This was recently changed by Redhat/Ansible + | to version "2.15.0".) + | 2) Update the list of resource modules in the README file to include all currently available + | resource modules for this collection. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 6c6dd1b91..fcf4728fc 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -721,3 +721,236 @@ releases: - 322-docs-README-updates.yaml - v2.4.0_summary.yaml release_date: '2024-01-08' + 2.5.0: + changes: + bugfixes: + - sonic_bfd - Fix BFD states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). + - sonic_bgp_neighbors - Fix issues with deleted state (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). + - sonic_copp - Fix CoPP states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/381). + - sonic_interfaces - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/377). + - sonic_interfaces - Fix replaced and overridden state handling for Loopback + interfaces (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). + - sonic_l2_interfaces - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/410). + - sonic_l3_interfaces - Fix replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/431). + - sonic_mac - Fix MAC states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). + - sonic_prefix_lists - Fix idempotency failure (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/354). + - sonic_prefix_lists - Fix replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/354). + - sonic_qos_pfc - Add back accidentally deleted line of code (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/391). + - sonic_static_routes - Fix static routes states implementation bug (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/383). + - sonic_vlans - Fix exception when gathering facts (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/377). + minor_changes: + - bgp_af - Add support for 'import vrf' commands (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/351). + - sonic_bfd - Add playbook check and diff modes support for bfd module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). + - sonic_bgp - Add playbook check and diff modes support for bgp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). + - sonic_bgp - Add support BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). + - sonic_bgp - Fix GitHub issue# 416 (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/418). + - sonic_bgp_af - Add playbook check and diff modes support for bgp_af module + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). + - sonic_bgp_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). + - sonic_bgp_af - Add support for aggregate address configuration(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/398). + - sonic_bgp_af - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/400) + - sonic_bgp_as_paths - Add playbook check and diff modes support for bgp_as_paths + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). + - sonic_bgp_communities - Add playbook check and diff modes support for bgp_communities + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). + - sonic_bgp_ext_communities - Add playbook check and diff modes support for + bgp_ext_communities module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/350). + - sonic_bgp_neighbors - Add playbook check and diff modes support for bgp_neighbors + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/360). + - sonic_bgp_neighbors - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). + - sonic_bgp_neighbors - Add support for replaced and overridden states (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/335). + - sonic_bgp_neighbors - Add support for replaced and overridden states (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/336). + - sonic_bgp_neighbors - Add support for the "fabric_external" option (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/336). + - sonic_bgp_neighbors_af - Add playbook check and diff modes support for bgp_neighbors_af + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/360). + - sonic_bgp_neighbors_af - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). + - sonic_copp - Add playbook check and diff modes support for copp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). + - sonic_dhcp_relay - Add playbook check and diff modes support for dhcp_relay + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). + - sonic_dhcp_snooping - Add playbook check and diff modes support for dhcp_snooping + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/346). + - sonic_interfaces - Add description, enabled option support for Loopback interfaces + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). + - sonic_interfaces - Fix GitHub issue 357 - set proper default value when deleted + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/366). + - sonic_interfaces - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/364). + - sonic_l3_interfaces - Add playbook check and diff modes support for l3_interfaces + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/328). + - sonic_l3_interfaces - Add support for USGv6R1 related features (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/374). + - sonic_l3_interfaces - Fix IPv6 default dad configuration handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/428). + - sonic_lag_interfaces - Add evpn ethernet-segment support for LAG interfaces + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/403). + - sonic_lldp_global - Add playbook check and diff modes support for lldp_global + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). + - sonic_logging - Add support for protocol option in logging module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/317). + - sonic_mac - Add playbook check and diff modes support for mac module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). + - sonic_mclag - Add playbook check and diff modes support for mclag module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). + - sonic_mclag - Enable session-vrf command support in mclag(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/299). + - sonic_port_breakout - Add playbook check and diff modes support for port_breakout + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). + - sonic_port_group - Make error message for port group facts gathering more + descriptive (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/396). + - sonic_prefix_lists - Add playbook check and diff modes support for prefix_lists + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). + - sonic_qos_maps - Comment out PFC priority group map tests cases (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/395). + - sonic_qos_scheduler - Update states implementation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/373). + - sonic_route_maps - Add UT for route maps module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/384). + - sonic_route_maps - Add playbook check and diff modes support for route_maps + module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). + - sonic_route_maps - Add support for BGP Asn Notation (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/417). + - sonic_route_maps - Add support for the 'set tag' option and synchronize module + documentation with argspec and model (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/413). + - sonic_stp - Add playbook check and diff modes support for stp module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/338). + - sonic_system - Add support for 'standard_extended' interface-naming mode (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/352). + - sonic_system - Add support for configuring auto-breakout feature (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/342). + - sonic_system - Adding Versatile Hash feature.(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/401). + - sonic_system - Enable auditd command support(https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/405). + - sonic_system - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/388). + - sonic_vxlan - Fix GitHub issue 376 - Change vxlan module get_fact function + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/393). + - sonic_vxlans - Add playbook check and diff modes support for vxlans module + (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/337). + - sonic_vxlans - Add support for the "external_ip" vxlan option (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/330). + release_summary: '| Release Date: 2024-0812 + + | + + | This release provides enhanced Dell Enterprise SONiC Ansible Collection + support for SONiC 4.x images. + + | In addition to new resource modules to support previously existing functionality, + it provides + + | support for several new features released in SONiC releases 4.1, 4.2, and + 4.4. + + | It also provides bug fixes and enhancements for support of features that + were initially introduced + + | in previous Enterprise SONiC Ansible releases. The changelog describes changes + made to the modules + + | included in this collection since release 2.0.0. + + | + + | Additional details are described below. + + | 1) Update the "requires_ansible" version in the meta/runtime.yml file for + this collection + + | to the oldest supported version of ansible-core. (This was recently changed + by Redhat/Ansible + + | to version "2.15.0".) + + | 2) Update the list of resource modules in the README file to include all + currently available + + | resource modules for this collection. + + ' + fragments: + - 299-mclag-session-vrf-support.yaml + - 317-added-protocol-option-for-syslog-module.yaml + - 328-playbook-check-diff-modes-for-l3-interfaces.yaml + - 329-regression-test-automated-intf-naming-config.yaml + - 330-add-external-ip-attribute-for-vxlan.yaml + - 331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml + - 335-replaced-overridden-for-bgp-neighbors-module.yaml + - 336-replaced-overridden-for-bgp-neighbors-af-module.yaml + - 337-playbook-check-diff-modes-for-mclag-port-breakout-vxlans.yaml + - 338-playbook-check-diff-modes-for-mac-stp-lldp.yaml + - 342-system-auto-breakout-module.yaml + - 344-update-ut-config-requests-validation.yaml + - 346-playbook-check-diff-modes-for-bfd-copp-dhcps.yaml + - 350-playbook-check-diff-modes-for-bgp-modules.yaml + - 351-bgp-af-import-vrf-support.yaml + - 352-system-standard-extended-interface-naming.yaml + - 354-prefix-lists-fix-replaced-idempotency.yaml + - 360-playbook-check-diff-modes-for-bgp-neighbors-and-af.yaml + - 364-interfaces-fix-loopback.yaml + - 366-github-issue-357-fix.yaml + - 373-qos-scheduler-update-states.yaml + - 374-usgv6r1-related-features-support.yaml + - 377-interfaces-vlans-fix-facts.yaml + - 381-fix-copp-states.yaml + - 383-fix-state-implementation.yaml + - 384-add-route-maps-ut.yaml + - 388-system-update-replaced-state.yaml + - 391-fix-qos-pfc-bug.yaml + - 393-github-issue-376-fix.yaml + - 394-update-version-remove-facts-reports.yaml + - 395-comment-out-pfc-pg-test-cases.yaml + - 396-port-group-facts-enhancement.yaml + - 398-bgp-af-aggregate-address-support.yaml + - 400-bgp-af-update-replaced-state.yaml + - 401-versatile-hash.yaml + - 403-lag-interface-evpn-ethernet-segment.yaml + - 405-auditd-config.yaml + - 410-sonic_l2_interfaces-fix-facts-exception.yaml + - 413-route-maps-set-tag-support-and-doc-updates.yaml + - 417-bgp-asn-notation.yaml + - 418-github-issue-416-fix.yaml + - 422-update-ut-for-bgp-neighbors.yaml + - 428-l3-interfaces-default-dad-fix.yaml + - 429-cliconf-doc-fix.yaml + - 431-l3-interfaces-fix-replaced-state.yaml + - 432-changelogs-v2.5.0-release-note.yaml + - v2.5.0_summary.yaml + modules: + - description: Configure global LDAP server settings on SONiC. + name: sonic_ldap + namespace: '' + - description: Manage Global Login Lockout configurations on SONiC. + name: sonic_login_lockout + namespace: '' + - description: Manage management servers configuration on SONiC. + name: sonic_mgmt_servers + namespace: '' + - description: configure OSPF area settings on SONiC. + name: sonic_ospf_area + namespace: '' + - description: Configure global OSPFv2 protocol settings on SONiC. + name: sonic_ospfv2 + namespace: '' + - description: Configure OSPFv2 interface mode protocol settings on SONiC. + name: sonic_ospfv2_interfaces + namespace: '' + - description: Manage global PIM configurations on SONiC. + name: sonic_pim_global + namespace: '' + - description: Manage interface-specific PIM configurations on SONiC. + name: sonic_pim_interfaces + namespace: '' + - description: Manage PoE configuration on SONiC. + name: sonic_poe + namespace: '' + - description: Manage QoS buffer configuration on SONiC. + name: sonic_qos_buffer + namespace: '' + - description: Manage QoS interfaces configuration on SONiC. + name: sonic_qos_interfaces + namespace: '' + - description: Manage QoS maps configuration on SONiC. + name: sonic_qos_maps + namespace: '' + - description: Manage QoS PFC configuration on SONiC. + name: sonic_qos_pfc + namespace: '' + - description: Manage QoS scheduler configuration on SONiC. + name: sonic_qos_scheduler + namespace: '' + - description: Manage QoS WRED profiles configuration on SONiC. + name: sonic_qos_wred + namespace: '' + - description: Manage RoCE QoS configuration on SONiC. + name: sonic_roce + namespace: '' + - description: configure sflow settings on SONiC. + name: sonic_sflow + namespace: '' + - description: Configure VRRP protocol settings on SONiC. + name: sonic_vrrp + namespace: '' + release_date: '2024-08-13' diff --git a/changelogs/config.yaml b/changelogs/config.yaml index 158fea109..071d27cf1 100644 --- a/changelogs/config.yaml +++ b/changelogs/config.yaml @@ -1,5 +1,8 @@ +add_plugin_period: true changelog_filename_template: ../CHANGELOG.rst changelog_filename_version_depth: 0 +changelog_nice_yaml: false +changelog_sort: alphanumerical changes_file: changelog.yaml changes_format: combined ignore_other_fragment_extensions: true @@ -8,6 +11,8 @@ archive_path_template: changelogs/archive_fragments/{version} mention_ancestor: true new_plugins_after_name: removed_features notesdir: fragments +output_formats: +- rst prelude_section_name: release_summary prelude_section_title: Release Summary sanitize_changelog: true diff --git a/changelogs/fragments/296-new-vlan-mapping-resource-module.yaml b/changelogs/fragments/296-new-vlan-mapping-resource-module.yaml new file mode 100644 index 000000000..ba74f2ad3 --- /dev/null +++ b/changelogs/fragments/296-new-vlan-mapping-resource-module.yaml @@ -0,0 +1,2 @@ +breaking_changes: + - sonic_vlan_mapping - New vlan_mapping resource module. The users of the vlan_mapping resource module with playbooks written for the SONiC 4.1 will need to revise their playbooks based on the new argspec to use those playbooks for SONiC 4.2 and later versions. (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/296). diff --git a/changelogs/fragments/382-aaa-breaking-change.yaml b/changelogs/fragments/382-aaa-breaking-change.yaml new file mode 100644 index 000000000..96b0c64b7 --- /dev/null +++ b/changelogs/fragments/382-aaa-breaking-change.yaml @@ -0,0 +1,2 @@ +breaking_changes: + - sonic_aaa - Update AAA module to align with SONiC functionality (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/382) diff --git a/changelogs/fragments/434-changelogs-post-release-2.5.0-pylint-and-doc-fixes.yaml b/changelogs/fragments/434-changelogs-post-release-2.5.0-pylint-and-doc-fixes.yaml new file mode 100644 index 000000000..5d97a4f6e --- /dev/null +++ b/changelogs/fragments/434-changelogs-post-release-2.5.0-pylint-and-doc-fixes.yaml @@ -0,0 +1,3 @@ +--- +trivial: + - changelogs - Fix a release 2.5.0 Pylint 2.7 error and and documentation inaccuracies (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/434). diff --git a/galaxy.yml b/galaxy.yml index 5f598df83..b46f97222 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: dellemc name: enterprise_sonic -version: 2.4.0 +version: 2.5.0 readme: README.md authors: - Senthil Kumar Ganesan diff --git a/meta/runtime.yml b/meta/runtime.yml index 877679f24..84fb234fe 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,4 +1,4 @@ -requires_ansible: '>=2.14.0' +requires_ansible: '>=2.15.0' plugin_routing: action: sonic_config: diff --git a/playbooks/common_examples/sonic_aaa.yaml b/playbooks/common_examples/sonic_aaa.yaml index 7d968b83c..41d434e1e 100644 --- a/playbooks/common_examples/sonic_aaa.yaml +++ b/playbooks/common_examples/sonic_aaa.yaml @@ -38,10 +38,39 @@ sonic_aaa: config: authentication: - data: - fail_through: true - group: tacacs+ - local: true + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local state: merged - name: Merge tacacs configurations sonic_tacacs_server: diff --git a/plugins/cliconf/sonic.py b/plugins/cliconf/sonic.py index e5cc7630f..c593c39fd 100644 --- a/plugins/cliconf/sonic.py +++ b/plugins/cliconf/sonic.py @@ -24,10 +24,10 @@ DOCUMENTATION = """ --- name: sonic -short_description: Use sonic cliconf to run command on Dell OS10 platform +short_description: Use sonic cliconf to run command on Dell SONiC platforms description: - This sonic plugin provides low level abstraction apis for - sending and receiving CLI commands from Dell OS10 network devices. + sending and receiving CLI commands on Dell SONiC network devices. """ import json diff --git a/plugins/module_utils/network/sonic/argspec/aaa/aaa.py b/plugins/module_utils/network/sonic/argspec/aaa/aaa.py index a61b7307b..97dbdf198 100644 --- a/plugins/module_utils/network/sonic/argspec/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/argspec/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -42,16 +42,57 @@ def __init__(self, **kwargs): 'options': { 'authentication': { 'options': { - 'data': { - 'options': { - 'fail_through': {'type': 'bool'}, - 'group': { - 'choices': ['ldap', 'radius', 'tacacs+'], - 'type': 'str' - }, - 'local': {'type': 'bool'} - }, - 'type': 'dict' + 'auth_method': { + 'choices': ['ldap', 'local', 'radius', 'tacacs+'], + 'elements': 'str', + 'type': 'list' + }, + 'console_auth_local': {'type': 'bool'}, + 'failthrough': {'type': 'bool'}, + }, + 'type': 'dict' + }, + 'authorization': { + 'options': { + 'commands_auth_method': { + 'choices': ['local', 'tacacs+'], + 'elements': 'str', + 'type': 'list' + }, + 'login_auth_method': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' + } + }, + 'type': 'dict' + }, + 'name_service': { + 'options': { + 'group': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'netgroup': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' + }, + 'passwd': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'shadow': { + 'choices': ['ldap', 'local', 'login'], + 'elements': 'str', + 'type': 'list' + }, + 'sudoers': { + 'choices': ['ldap', 'local'], + 'elements': 'str', + 'type': 'list' } }, 'type': 'dict' diff --git a/plugins/module_utils/network/sonic/argspec/bgp/bgp.py b/plugins/module_utils/network/sonic/argspec/bgp/bgp.py index 8d494dddd..854e73d71 100644 --- a/plugins/module_utils/network/sonic/argspec/bgp/bgp.py +++ b/plugins/module_utils/network/sonic/argspec/bgp/bgp.py @@ -67,6 +67,10 @@ def __init__(self, **kwargs): 'bgp_as': {'required': True, 'type': 'str'}, 'log_neighbor_changes': {'type': 'bool'}, 'router_id': {'type': 'str'}, + 'as_notation': { + 'choices': ['asdot', 'asdot+'], + 'type': 'str' + }, "max_med": { "options": { "on_startup": { diff --git a/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py b/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py index 0ef83d480..2af6eab14 100644 --- a/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py +++ b/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -71,6 +71,16 @@ def __init__(self, **kwargs): 'required': True, 'type': 'str' }, + 'aggregate_address_config': { + 'elements': 'dict', + 'options': { + 'prefix': {'required': True, 'type': 'str'}, + 'as_set': {'type': 'bool'}, + 'policy_name': {'type': 'str'}, + 'summary_only': {'type': 'bool'} + }, + 'type': 'list' + }, 'rd': {'type': 'str'}, 'rt_in': {'type': 'list', 'elements': 'str'}, 'rt_out': {'type': 'list', 'elements': 'str'}, diff --git a/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py b/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py index 02e695fb4..6cebc88c7 100644 --- a/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py +++ b/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py @@ -50,7 +50,7 @@ def __init__(self, **kwargs): 'mutually_exclusive': [['peer_type', 'peer_as']], 'options': { 'peer_type': {'type': 'str', 'choices': ['internal', 'external']}, - 'peer_as': {'type': 'int'}, + 'peer_as': {'type': 'str'}, }, 'type': 'dict' }, @@ -101,14 +101,14 @@ def __init__(self, **kwargs): 'local_address': {'type': 'str'}, 'local_as': { 'options': { - 'as': {'required': True, 'type': 'int'}, + 'as': {'required': True, 'type': 'str'}, 'no_prepend': {'type': 'bool'}, 'replace_as': {'type': 'bool'}, }, 'type': 'dict' }, 'override_capability': {'type': 'bool'}, - 'passive': {'default': 'False', 'type': 'bool'}, + 'passive': {'type': 'bool'}, 'port': {'type': 'int'}, 'shutdown_msg': {'type': 'str'}, 'solo': {'type': 'bool'}, @@ -126,7 +126,7 @@ def __init__(self, **kwargs): 'mutually_exclusive': [['peer_type', 'peer_as']], 'options': { 'peer_type': {'type': 'str', 'choices': ['internal', 'external']}, - 'peer_as': {'type': 'int'}, + 'peer_as': {'type': 'str'}, }, 'type': 'dict' }, @@ -138,7 +138,8 @@ def __init__(self, **kwargs): 'activate': {'type': 'bool'}, 'afi': { 'choices': ['ipv4', 'ipv6', 'l2vpn'], - 'type': 'str' + 'type': 'str', + 'required': True }, 'allowas_in': { 'mutually_exclusive': [['origin', 'value']], @@ -223,14 +224,14 @@ def __init__(self, **kwargs): 'local_address': {'type': 'str'}, 'local_as': { 'options': { - 'as': {'required': True, 'type': 'int'}, + 'as': {'required': True, 'type': 'str'}, 'no_prepend': {'type': 'bool'}, 'replace_as': {'type': 'bool'}, }, 'type': 'dict' }, 'override_capability': {'type': 'bool'}, - 'passive': {'default': 'False', 'type': 'bool'}, + 'passive': {'type': 'bool'}, 'shutdown_msg': {'type': 'str'}, 'solo': {'type': 'bool'}, 'strict_capability_match': {'type': 'bool'}, @@ -243,7 +244,7 @@ def __init__(self, **kwargs): 'type': 'list' }, 'state': { - 'choices': ['merged', 'deleted'], + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], 'default': 'merged' } } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/facts/facts.py b/plugins/module_utils/network/sonic/argspec/facts/facts.py index 457f390a3..21c32be3f 100644 --- a/plugins/module_utils/network/sonic/argspec/facts/facts.py +++ b/plugins/module_utils/network/sonic/argspec/facts/facts.py @@ -33,15 +33,19 @@ def __init__(self, **kwargs): 'bgp_as_paths', 'bgp_communities', 'bgp_ext_communities', + 'ospfv2_interfaces', + 'ospfv2', 'mclag', 'prefix_lists', 'vlan_mapping', 'vrfs', + 'vrrp', 'vxlans', 'users', 'system', 'port_breakout', 'aaa', + 'ldap', 'tacacs_server', 'radius_server', 'static_routes', @@ -74,7 +78,9 @@ def __init__(self, **kwargs): 'pim_global', 'pim_interfaces', 'login_lockout', - 'poe' + 'poe', + 'mgmt_servers', + 'ospf_area' ] argument_spec = { diff --git a/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py index b32d7f92e..4c89e8104 100644 --- a/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py @@ -61,10 +61,13 @@ def __init__(self, **kwargs): 'addresses': { 'elements': 'dict', 'options': { - 'address': {'type': 'str'} + 'address': {'type': 'str'}, + 'eui64': {'default': 'False', 'type': 'bool'} }, 'type': 'list' }, + 'autoconf': {'type': 'bool'}, + 'dad': {'choices': ['ENABLE', 'DISABLE', 'DISABLE_IPV6_ON_FAILURE'], 'type': 'str'}, 'enabled': {'type': 'bool'} }, 'type': 'dict' diff --git a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py index fc349232f..db984758d 100644 --- a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py @@ -55,7 +55,19 @@ def __init__(self, **kwargs): "type": "dict" }, "name": {"required": True, "type": "str"}, - "mode": {"type": "str", "choices": ["static", "lacp"]} + "mode": {"type": "str", "choices": ["static", "lacp"]}, + "ethernet_segment": { + "options": { + "esi_type": {"required": True, + "choices": ["auto_lacp", + "auto_system_mac", + "ethernet_segment_id"], + "type": "str"}, + "esi": {"type": "str"}, + "df_preference": {"type": "int"}, + }, + "type": "dict" + } }, "type": "list" }, diff --git a/plugins/module_utils/network/sonic/argspec/ldap/__init__.py b/plugins/module_utils/network/sonic/argspec/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/ldap/ldap.py b/plugins/module_utils/network/sonic/argspec/ldap/ldap.py new file mode 100644 index 000000000..248ae9158 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/ldap/ldap.py @@ -0,0 +1,169 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_ldap module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class LdapArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_ldap module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'base': {'type': 'str'}, + 'bind_timelimit': {'type': 'int'}, + 'binddn': {'type': 'str'}, + 'bindpw': { + 'options': { + 'pwd': {'required': True, 'type': 'str'}, + 'encrypted': {'type': 'bool'}, + }, + 'type': 'dict' + }, + 'idle_timelimit': {'type': 'int'}, + 'map': { + 'options': { + 'attribute': { + 'elements': 'dict', + 'options': { + 'from': {'type': 'str'}, + 'to': {'type': 'str'} + }, + 'required_together': [['from', 'to']], + 'type': 'list' + }, + 'default_attribute': { + 'elements': 'dict', + 'options': { + 'from': {'type': 'str'}, + 'to': {'type': 'str'} + }, + 'required_together': [['from', 'to']], + 'type': 'list' + }, + 'map_remote_groups_to_sonic_roles': { + 'elements': 'dict', + 'options': { + 'remote_group': {'type': 'str'}, + 'sonic_roles': { + 'type': 'list', + 'elements': 'str', + 'choices': ['admin', 'operator', 'netadmin', 'secadmin'] + } + }, + 'required_together': [['remote_group', 'sonic_roles']], + 'type': 'list' + }, + 'objectclass': { + 'elements': 'dict', + 'options': { + 'from': {'type': 'str'}, + 'to': {'type': 'str'} + }, + 'required_together': [['from', 'to']], + 'type': 'list' + }, + 'override_attribute': { + 'elements': 'dict', + 'options': { + 'from': {'type': 'str'}, + 'to': {'type': 'str'} + }, + 'required_together': [['from', 'to']], + 'type': 'list' + } + }, + 'type': 'dict' + }, + 'name': { + 'choices': ['global', 'nss', 'pam', 'sudo'], + 'required': True, + 'type': 'str' + }, + 'nss_base_group': {'type': 'str'}, + 'nss_base_netgroup': {'type': 'str'}, + 'nss_base_passwd': {'no_log': True, 'type': 'str'}, + 'nss_base_shadow': {'type': 'str'}, + 'nss_base_sudoers': {'type': 'str'}, + 'nss_initgroups_ignoreusers': {'type': 'str'}, + 'nss_skipmembers': {'type': 'bool'}, + 'pam_filter': {'type': 'str'}, + 'pam_group_dn': {'type': 'str'}, + 'pam_login_attribute': {'type': 'str'}, + 'pam_member_attribute': {'type': 'str'}, + 'port': {'type': 'int'}, + 'retry': {'type': 'int'}, + 'scope': { + 'choices': ['sub', 'one', 'base'], + 'type': 'str' + }, + 'servers': { + 'elements': 'dict', + 'options': { + 'address': {'required': True, 'type': 'str'}, + 'port': {'type': 'int'}, + 'priority': {'type': 'int'}, + 'retry': {'type': 'int'}, + 'server_type': { + 'choices': ['all', 'nss', 'sudo', 'pam', 'nss_sudo', 'nss_pam', 'sudo_pam'], + 'type': 'str' + }, + 'ssl': { + 'choices': ['on', 'off', 'start_tls'], + 'type': 'str' + } + }, + 'type': 'list' + }, + 'source_interface': {'type': 'str'}, + 'ssl': { + 'choices': ['on', 'off', 'start_tls'], + 'type': 'str' + }, + 'sudoers_base': {'type': 'str'}, + 'sudoers_search_filter': {'type': 'str'}, + 'timelimit': {'type': 'int'}, + 'version': { + 'choices': [2, 3], + 'type': 'int' + }, + 'vrf': {'type': 'str'} + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/mgmt_servers/mgmt_servers.py b/plugins/module_utils/network/sonic/argspec/mgmt_servers/mgmt_servers.py new file mode 100644 index 000000000..5014a4699 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/mgmt_servers/mgmt_servers.py @@ -0,0 +1,77 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_mgmt_servers module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Mgmt_serversArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_mgmt_servers module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'options': { + 'rest': { + 'options': { + 'api_timeout': {'default': 900, 'type': 'int'}, + 'client_auth': {'default': 'password,jwt', 'type': 'str'}, + 'log_level': {'default': 0, 'type': 'int'}, + 'port': {'default': 443, 'type': 'int'}, + 'read_timeout': {'default': 15, 'type': 'int'}, + 'req_limit': {'type': 'int'}, + 'security_profile': {'type': 'str'}, + 'shutdown': {'type': 'bool'}, + 'vrf': {'choices': ['mgmt'], 'type': 'str'} + }, + 'type': 'dict' + }, + 'telemetry': { + 'options': { + 'api_timeout': {'default': 0, 'type': 'int'}, + 'client_auth': {'default': 'password,jwt', 'type': 'str'}, + 'jwt_refresh': {'default': 900, 'type': 'int'}, + 'jwt_valid': {'default': 3600, 'type': 'int'}, + 'log_level': {'default': 0, 'type': 'int'}, + 'port': {'default': 8080, 'type': 'int'}, + 'security_profile': {'type': 'str'}, + 'vrf': {'choices': ['mgmt'], 'type': 'str'} + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'state': { + 'choices': ['merged', 'deleted', 'overridden', 'replaced'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/ospf_area/ospf_area.py b/plugins/module_utils/network/sonic/argspec/ospf_area/ospf_area.py new file mode 100644 index 000000000..d95786bd7 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/ospf_area/ospf_area.py @@ -0,0 +1,112 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_ospf_area module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Ospf_areaArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_ospf_area module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'area_id': {'required': True, 'type': 'str'}, + 'vrf_name': {'default': 'default', 'type': 'str'}, + 'authentication_type': {'type': 'str', 'choices': ["message_digest", "text"]}, + 'default_cost': {'type': 'int'}, + 'filter_list_in': {'type': 'str'}, + 'filter_list_out': {'type': 'str'}, + 'networks': { + 'elements': 'str', + 'type': 'list' + }, + 'ranges': { + 'elements': 'dict', + 'options': { + 'prefix': {'required': True, 'type': 'str'}, + 'advertise': {'type': 'bool'}, + 'cost': {'type': 'int'}, + 'substitute': {'type': 'str'} + }, + 'type': 'list' + }, + 'shortcut': {'type': 'str', 'choices': ['default', 'enable', 'disable']}, + 'stub': { + 'options': { + 'enabled': {'type': 'bool'}, + 'no_summary': {'type': 'bool'} + }, + 'type': 'dict' + }, + 'virtual_links': { + 'elements': 'dict', + 'options': { + 'router_id': {'type': 'str', 'required': True}, + 'enabled': {'type': 'bool'}, + 'dead_interval': {'type': 'int'}, + 'hello_interval': {'type': 'int'}, + 'retransmit_interval': {'type': 'int'}, + 'transmit_delay': {'type': 'int'}, + 'authentication': { + 'options': { + 'auth_type': { + 'type': 'str', + 'choices': ['message_digest', 'text', 'none'] + }, + 'key': {'type': 'str', 'no_log': True}, + 'key_encrypted': {'type': 'bool'} + }, + 'type': 'dict' + }, + 'message_digest_list': { + 'elements': 'dict', + 'options': { + 'key_id': {'type': 'int', 'required': True}, + 'key': {'type': 'str', 'no_log': True}, + 'key_encrypted': {'type': 'bool'} + }, + 'type': 'list' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/ospfv2/__init__.py b/plugins/module_utils/network/sonic/argspec/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/sonic/argspec/ospfv2/ospfv2.py new file mode 100644 index 000000000..9f61ef763 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/ospfv2/ospfv2.py @@ -0,0 +1,158 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_ospfv2 module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the sonic_ospfv2 module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'mutually_exclusive': [['non_passive_interfaces', 'passive_interfaces']], + 'options': { + 'abr_type': { + 'choices': ['cisco', 'ibm', 'shortcut', 'standard'], + 'type': 'str' + }, + 'auto_cost_reference_bandwidth': {'type': 'int'}, + 'default_metric': {'type': 'int'}, + 'default_passive': {'type': 'bool'}, + 'distance': { + 'options': { + 'all': {'type': 'int'}, + 'external': {'type': 'int'}, + 'inter_area': {'type': 'int'}, + 'intra_area': {'type': 'int'} + }, + 'type': 'dict' + }, + 'graceful_restart': { + 'options': { + 'grace_period': {'type': 'int'}, + 'enable': {'type': 'bool'}, + 'helper': { + 'options': { + 'enable': {'type': 'bool'}, + 'advertise_router_id': {'elements': 'str', 'type': 'list'}, + 'planned_only': {'type': 'bool'}, + 'strict_lsa_checking': {'type': 'bool'}, + 'supported_grace_time': {'type': 'int'} + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'log_adjacency_changes': {'choices': ['brief', 'detail'], 'type': 'str'}, + 'max_metric': { + 'options': { + 'administrative': {'type': 'bool'}, + 'external_lsa_all': {'type': 'int'}, + 'external_lsa_connected': {'type': 'int'}, + 'on_startup': {'type': 'int'}, + 'router_lsa_all': {'type': 'int'}, + 'router_lsa_stub': {'type': 'int'} + }, + 'type': 'dict' + }, + 'maximum_paths': {'type': 'int'}, + 'non_passive_interfaces': { + 'elements': 'dict', + 'options': { + 'addresses': { + 'elements': 'str', + 'type': 'list' + }, + 'interface': {'required': True, 'type': 'str'} + }, + 'type': 'list' + }, + 'opaque_lsa_capability': {'type': 'bool'}, + 'passive_interfaces': { + 'elements': 'dict', + 'options': { + 'addresses': { + 'elements': 'str', + 'type': 'list' + }, + 'interface': {'required': True, 'type': 'str'} + }, + 'type': 'list' + }, + 'redistribute': { + 'elements': 'dict', + 'options': { + 'always': {'type': 'bool'}, + 'metric': {'type': 'int'}, + 'metric_type': {'choices': [1, 2], 'type': 'int'}, + 'protocol': { + 'choices': ['bgp', 'kernel', 'connected', 'static', 'default_route'], + 'required': True, + 'type': 'str' + }, + 'route_map': {'type': 'str'} + }, + 'type': 'list' + }, + 'refresh_timer': {'type': 'int'}, + 'rfc1583_compatible': {'type': 'bool'}, + 'router_id': {'type': 'str'}, + 'timers': { + 'options': { + 'lsa_min_arrival': {'type': 'int'}, + 'throttle_lsa_all': {'type': 'int'}, + 'throttle_spf': { + 'options': { + 'delay_time': {'type': 'int'}, + 'initial_hold_time': {'type': 'int'}, + 'maximum_hold_time': {'type': 'int'} + }, + 'required_together': [['delay_time', 'initial_hold_time', 'maximum_hold_time']], + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'vrf_name': {'default': 'default', 'type': 'str'}, + 'write_multiplier': {'type': 'int'} + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/ospfv2_interfaces/__init__.py b/plugins/module_utils/network/sonic/argspec/ospfv2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/ospfv2_interfaces/ospfv2_interfaces.py b/plugins/module_utils/network/sonic/argspec/ospfv2_interfaces/ospfv2_interfaces.py new file mode 100644 index 000000000..681275cd9 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/ospfv2_interfaces/ospfv2_interfaces.py @@ -0,0 +1,100 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_ospfv2_interfaces module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Ospfv2_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_ospfv2_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'bfd': { + 'options': { + 'bfd_profile': {'type': 'str'}, + 'enable': {'required': True, 'type': 'bool'} + }, + 'type': 'dict' + }, + 'name': {'required': True, 'type': 'str'}, + 'network': { + 'choices': ['broadcast', 'point_to_point'], + 'type': 'str' + }, + 'ospf_attributes': { + 'elements': 'dict', + 'mutually_exclusive': [['dead_interval', 'hello_multiplier']], + 'options': { + 'address': {'type': 'str'}, + 'area_id': {'type': 'str'}, + 'authentication': { + 'options': { + 'encrypted': {'type': 'bool'}, + 'password': {'no_log': True, 'required': True, 'type': 'str'} + }, + 'type': 'dict' + }, + 'authentication_type': { + 'choices': ['MD5HMAC', 'NONE', 'TEXT'], + 'type': 'str' + }, + 'cost': {'type': 'int'}, + 'dead_interval': {'type': 'int'}, + 'hello_interval': {'type': 'int'}, + 'hello_multiplier': {'type': 'int'}, + 'md_authentication': { + 'elements': 'dict', + 'options': { + 'encrypted': {'type': 'bool'}, + 'key_id': {'required': True, 'type': 'int'}, + 'md5key': {'no_log': True, 'type': 'str'} + }, + 'type': 'list' + }, + 'mtu_ignore': {'type': 'bool'}, + 'priority': {'type': 'int'}, + 'retransmit_interval': {'type': 'int'}, + 'transmit_delay': {'type': 'int'} + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py b/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py index f36fbbcb0..ddbb31007 100644 --- a/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py +++ b/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py @@ -180,7 +180,8 @@ def __init__(self, **kwargs): 'choices': ['egp', 'igp', 'incomplete'], 'type': 'str' }, - 'weight': {'type': 'int'} + 'weight': {'type': 'int'}, + 'tag': {'type': 'int'} }, 'type': 'dict' }, diff --git a/plugins/module_utils/network/sonic/argspec/system/system.py b/plugins/module_utils/network/sonic/argspec/system/system.py index 259bcc09b..74cc19666 100644 --- a/plugins/module_utils/network/sonic/argspec/system/system.py +++ b/plugins/module_utils/network/sonic/argspec/system/system.py @@ -49,6 +49,10 @@ def __init__(self, **kwargs): 'type': 'dict' }, 'hostname': {'type': 'str'}, + 'audit_rules' : { + 'choices': ['BASIC', 'DETAIL', 'CUSTOM', 'NONE'], + 'type': 'str' + }, 'interface_naming': { 'choices': ['standard', 'standard_extended', 'native'], 'type': 'str' @@ -56,6 +60,10 @@ def __init__(self, **kwargs): 'auto_breakout': { 'choices': ['ENABLE', 'DISABLE'], 'type': 'str' + }, + 'load_share_hash_algo': { + 'choices': ['CRC', 'XOR', 'CRC_32LO', 'CRC_32HI', 'CRC_CCITT', 'CRC_XOR', 'JENKINS_HASH_LO', 'JENKINS_HASH_HI'], + 'type': 'str' } }, 'type': 'dict' diff --git a/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py b/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py index ced5833fa..03f5400f5 100644 --- a/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py +++ b/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py @@ -43,12 +43,39 @@ def __init__(self, **kwargs): 'options': { 'mapping': { 'elements': 'dict', + 'mutually_exclusive': [['dot1q_tunnel', 'vlan_translation']], 'options': { - 'dot1q_tunnel': {'type': 'bool', 'default': False}, - 'inner_vlan': {'type': 'int'}, - 'priority': {'type': 'int'}, 'service_vlan': {'required': True, 'type': 'int'}, - 'vlan_ids': {'elements': 'str', 'type': 'list'} + 'dot1q_tunnel': { + 'options': { + 'vlan_ids': {'elements': 'str', 'type': 'list'}, + 'priority': {'type': 'int'} + }, + 'type': 'dict' + }, + 'vlan_translation': { + 'options': { + 'multi_tag': {'type': 'bool'}, + 'match_single_tags': { + 'elements': 'dict', + 'options': { + 'outer_vlan': {'required': True, 'type': 'int'}, + 'priority': {'type': 'int'} + }, + 'type': 'list' + }, + 'match_double_tags': { + 'elements': 'dict', + 'options': { + 'inner_vlan': {'required': True, 'type': 'int'}, + 'outer_vlan': {'required': True, 'type': 'int'}, + 'priority': {'type': 'int'} + }, + 'type': 'list' + } + }, + 'type': 'dict' + } }, 'type': 'list' }, diff --git a/plugins/module_utils/network/sonic/argspec/vrrp/__init__.py b/plugins/module_utils/network/sonic/argspec/vrrp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/vrrp/vrrp.py b/plugins/module_utils/network/sonic/argspec/vrrp/vrrp.py new file mode 100644 index 000000000..381641e1e --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/vrrp/vrrp.py @@ -0,0 +1,98 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_vrrp module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class VrrpArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_vrrp module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'group': { + 'elements': 'dict', + 'options': { + 'advertisement_interval': {'type': 'int'}, + 'afi': { + 'choices': ['ipv4', 'ipv6'], + 'required': True, + 'type': 'str' + }, + 'preempt': {'type': 'bool'}, + 'priority': {'type': 'int'}, + 'track_interface': { + 'elements': 'dict', + 'options': { + 'interface': { + 'required': True, + 'type': 'str' + }, + 'priority_increment': {'type': 'int'} + }, + 'required_together': [['interface', 'priority_increment']], + 'type': 'list' + }, + 'use_v2_checksum': {'type': 'bool'}, + 'version': { + 'choices': [2, 3], + 'type': 'int' + }, + 'virtual_address': { + 'elements': 'dict', + 'options': { + 'address': {'type': 'str'} + }, + 'type': 'list' + }, + 'virtual_router_id': { + 'required': True, + 'type': 'int' + } + }, + 'type': 'list' + }, + 'name': { + 'required': True, + 'type': 'str' + } + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'overridden', 'replaced'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/config/aaa/aaa.py b/plugins/module_utils/network/sonic/config/aaa/aaa.py index 036567f0a..b794a959c 100644 --- a/plugins/module_utils/network/sonic/config/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/config/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -13,6 +13,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) @@ -21,21 +22,21 @@ ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( - update_states, - get_diff, -) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( - utils, + remove_empties, + update_states ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( - to_request, - edit_config + edit_config, + to_request ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( get_new_config, get_formatted_config_diff ) +AAA_AUTHENTICATION_PATH = '/data/openconfig-system:system/aaa/authentication/config' +AAA_AUTHORIZATION_PATH = '/data/openconfig-system:system/aaa/authorization' +AAA_NAME_SERVICE_PATH = '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' PATCH = 'patch' DELETE = 'delete' @@ -66,7 +67,7 @@ def get_aaa_facts(self): facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) aaa_facts = facts['ansible_network_resources'].get('aaa') if not aaa_facts: - return [] + return {} return aaa_facts def execute_module(self): @@ -76,14 +77,17 @@ def execute_module(self): :returns: The result from module execution """ result = {'changed': False} - warnings = list() - commands = list() + warnings = [] + commands = [] existing_aaa_facts = self.get_aaa_facts() commands, requests = self.set_config(existing_aaa_facts) if commands and len(requests) > 0: if not self._module.check_mode: - self.edit_config(requests) + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) result['changed'] = True result['commands'] = commands @@ -98,6 +102,7 @@ def execute_module(self): if self._module.check_mode: result.pop('after', None) new_config = get_new_config(commands, existing_aaa_facts) + self.post_process_generated_config(new_config) result['after(generated)'] = new_config if self._module._diff: result['diff'] = get_formatted_config_diff(old_config, @@ -106,12 +111,6 @@ def execute_module(self): result['warnings'] = warnings return result - def edit_config(self, requests): - try: - response = edit_config(self._module, to_request(self._module, requests)) - except ConnectionError as exc: - self._module.fail_json(msg=str(exc), code=exc.code) - def set_config(self, existing_aaa_facts): """ Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) @@ -120,7 +119,7 @@ def set_config(self, existing_aaa_facts): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - want = self._module.params['config'] + want = remove_empties(self._module.params['config']) have = existing_aaa_facts resp = self.set_state(want, have) return to_list(resp) @@ -134,23 +133,20 @@ def set_state(self, want, have): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + commands = [] + requests = [] state = self._module.params['state'] - if not want: - want = {} - if not have: - have = {} - diff = self.get_diff_aaa(want, have) - if state == 'deleted': - commands = self._state_deleted(want, have) - elif state == 'merged': - commands = self._state_merged(diff) + if state == 'merged': + commands, requests = self._state_merged(diff) elif state == 'replaced': - commands = self._state_replaced(diff) + commands, requests = self._state_replaced(want, have, diff) elif state == 'overridden': - commands = self._state_overridden(want, have) - return commands + commands, requests = self._state_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + return commands, requests def _state_merged(self, diff): """ The command generator when state is merged @@ -159,40 +155,17 @@ def _state_merged(self, diff): :returns: the commands necessary to merge the provided into the current configuration """ - commands = [] - requests = [] - if diff: - requests = self.get_create_aaa_request(diff) - if len(requests) > 0: - commands = update_states(diff, "merged") - return commands, requests - - def _state_deleted(self, want, have): - """ The command generator when state is deleted + commands = diff + requests = self.get_modify_aaa_requests(commands) - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects - """ - commands = [] - requests = [] - if not want: - if have: - requests = self.get_delete_all_aaa_request(have) - if len(requests) > 0: - commands = update_states(have, "deleted") + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') else: - want = utils.remove_empties(want) - new_have = self.remove_default_entries(have) - d_diff = get_diff(want, new_have, is_skeleton=True) - diff_want = get_diff(want, d_diff, is_skeleton=True) - if diff_want: - requests = self.get_delete_all_aaa_request(diff_want) - if len(requests) > 0: - commands = update_states(diff_want, "deleted") + commands = [] + return commands, requests - def _state_replaced(self, diff): + def _state_replaced(self, want, have, diff): """ The command generator when state is merged :rtype: A list @@ -200,11 +173,25 @@ def _state_replaced(self, diff): the current configuration """ commands = [] + mod_commands = [] requests = [] - if diff: - requests = self.get_create_aaa_request(diff) - if len(requests) > 0: - commands = update_states(diff, "replaced") + replaced_config = self.get_replaced_config(want, have) + + if replaced_config: + is_delete_all = replaced_config == have + del_requests = self.get_delete_aaa_requests(replaced_config, have, is_delete_all) + requests.extend(del_requests) + commands.extend(update_states(replaced_config, 'deleted')) + mod_commands = want + else: + mod_commands = diff + + if mod_commands: + mod_requests = self.get_modify_aaa_requests(mod_commands) + if mod_requests: + requests.extend(mod_requests) + commands.extend(update_states(mod_commands, 'replaced')) + return commands, requests def _state_overridden(self, want, have): @@ -220,135 +207,344 @@ def _state_overridden(self, want, have): requests = [] if have and have != want: - del_requests = self.get_delete_all_aaa_request(have) + is_delete_all = True + del_requests = self.get_delete_aaa_requests(have, None, is_delete_all) requests.extend(del_requests) - commands.extend(update_states(have, "deleted")) + commands.extend(update_states(have, 'deleted')) have = [] if not have and want: mod_commands = want - mod_requests = self.get_create_aaa_request(mod_commands) + mod_requests = self.get_modify_aaa_requests(mod_commands) - if len(mod_requests) > 0: + if mod_requests: requests.extend(mod_requests) - commands.extend(update_states(mod_commands, "overridden")) + commands.extend(update_states(mod_commands, 'overridden')) return commands, requests - def get_create_aaa_request(self, commands): - requests = [] - aaa_path = 'data/openconfig-system:system/aaa' - method = PATCH - aaa_payload = self.build_create_aaa_payload(commands) - if aaa_payload: - request = {'path': aaa_path, 'method': method, 'data': aaa_payload} - requests.append(request) - return requests + def _state_deleted(self, want, have): + """ The command generator when state is deleted - def build_create_aaa_payload(self, commands): - payload = {} - auth_method_list = [] - if "authentication" in commands and commands["authentication"]: - payload = {"openconfig-system:aaa": {"authentication": {"config": {}}}} - if "local" in commands["authentication"]["data"] and commands["authentication"]["data"]["local"]: - auth_method_list.append('local') - if "group" in commands["authentication"]["data"] and commands["authentication"]["data"]["group"]: - auth_method = commands["authentication"]["data"]["group"] - auth_method_list.append(auth_method) - if auth_method_list: - cfg = {'authentication-method': auth_method_list} - payload['openconfig-system:aaa']['authentication']['config'].update(cfg) - if "fail_through" in commands["authentication"]["data"]: - cfg = {'failthrough': str(commands["authentication"]["data"]["fail_through"])} - payload['openconfig-system:aaa']['authentication']['config'].update(cfg) - return payload - - def remove_default_entries(self, data): - new_data = {} - if not data: - return new_data + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + is_delete_all = False + + if not want: + commands = deepcopy(have) + is_delete_all = True + else: + commands = deepcopy(want) + + requests = self.get_delete_aaa_requests(commands, have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') else: - new_data = {'authentication': {'data': {}}} - local = data['authentication']['data'].get('local', None) - if local is not None: - new_data["authentication"]["data"]["local"] = local - group = data['authentication']['data'].get('group', None) - if group is not None: - new_data["authentication"]["data"]["group"] = group - fail_through = data['authentication']['data'].get('fail_through', None) - if fail_through is not None: - new_data["authentication"]["data"]["fail_through"] = fail_through - return new_data - - def get_delete_all_aaa_request(self, have): + commands = [] + + return commands, requests + + def get_modify_aaa_requests(self, commands): requests = [] - if "authentication" in have and have["authentication"]: - if "local" in have["authentication"]["data"] or "group" in have["authentication"]["data"]: - request = self.get_authentication_method_delete_request() - requests.append(request) - if "fail_through" in have["authentication"]["data"]: - request = self.get_failthrough_delete_request() - requests.append(request) + + if commands: + # Authentication modification handling + authentication = commands.get('authentication') + if authentication: + authentication_cfg_dict = {} + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + if auth_method: + authentication_cfg_dict['authentication-method'] = auth_method + if console_auth_local is not None: + authentication_cfg_dict['console-authentication-local'] = console_auth_local + if failthrough is not None: + authentication_cfg_dict['failthrough'] = str(failthrough) + if authentication_cfg_dict: + payload = {'openconfig-system:config': authentication_cfg_dict} + requests.append({'path': AAA_AUTHENTICATION_PATH, 'method': PATCH, 'data': payload}) + + # Authorization modification handling + authorization = commands.get('authorization') + if authorization: + authorization_dict = {} + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + if commands_auth_method: + authorization_dict['openconfig-aaa-tacacsplus-ext:commands'] = {'config': {'authorization-method': commands_auth_method}} + if login_auth_method: + authorization_dict['openconfig-aaa-ext:login'] = {'config': {'authorization-method': login_auth_method}} + if authorization_dict: + payload = {'openconfig-system:authorization': authorization_dict} + requests.append({'path': AAA_AUTHORIZATION_PATH, 'method': PATCH, 'data': payload}) + + # Name-service modification handling + name_service = commands.get('name_service') + if name_service: + name_service_cfg_dict = {} + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + if group: + name_service_cfg_dict['group-method'] = group + if netgroup: + name_service_cfg_dict['netgroup-method'] = netgroup + if passwd: + name_service_cfg_dict['passwd-method'] = passwd + if shadow: + name_service_cfg_dict['shadow-method'] = shadow + if sudoers: + name_service_cfg_dict['sudoers-method'] = sudoers + if name_service_cfg_dict: + payload = {'openconfig-aaa-ext:config': name_service_cfg_dict} + requests.append({'path': AAA_NAME_SERVICE_PATH, 'method': PATCH, 'data': payload}) + return requests - def get_authentication_method_delete_request(self): - path = 'data/openconfig-system:system/aaa/authentication/config/authentication-method' - method = DELETE - request = {'path': path, 'method': method} - return request + def get_delete_aaa_requests(self, commands, have, is_delete_all): + requests = [] - def get_failthrough_delete_request(self): - path = 'data/openconfig-system:system/aaa/authentication/config/failthrough' - method = DELETE - request = {'path': path, 'method': method} + if not commands: + return requests + + if is_delete_all: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, None)) + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, None)) + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, None)) + return requests + + config_dict = {} + # Authentication deletion handling + authentication = commands.get('authentication') + if authentication: + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + cfg_authentication = have.get('authentication') + if cfg_authentication: + authentication_dict = {} + cfg_auth_method = cfg_authentication.get('auth_method') + cfg_console_auth_local = cfg_authentication.get('console_auth_local') + cfg_failthrough = cfg_authentication.get('failthrough') + + # Current SONiC behavior doesn't support single list item deletion + if auth_method and auth_method == cfg_auth_method: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'authentication-method')) + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None and console_auth_local == cfg_console_auth_local: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'console-authentication-local')) + authentication_dict['console_auth_local'] = console_auth_local + if failthrough is not None and failthrough == cfg_failthrough: + requests.append(self.get_delete_request(AAA_AUTHENTICATION_PATH, 'failthrough')) + authentication_dict['failthrough'] = failthrough + if authentication_dict: + config_dict['authentication'] = authentication_dict + + # Authorization deletion handling + authorization = commands.get('authorization') + if authorization: + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + cfg_authorization = have.get('authorization') + if cfg_authorization: + authorization_dict = {} + cfg_commands_auth_method = cfg_authorization.get('commands_auth_method') + cfg_login_auth_method = cfg_authorization.get('login_auth_method') + + # Current SONiC behavior doesn't support single list item deletion + if commands_auth_method and commands_auth_method == cfg_commands_auth_method: + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, + 'openconfig-aaa-tacacsplus-ext:commands/config/authorization-method')) + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method and login_auth_method == cfg_login_auth_method: + requests.append(self.get_delete_request(AAA_AUTHORIZATION_PATH, 'openconfig-aaa-ext:login/config/authorization-method')) + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + config_dict['authorization'] = authorization_dict + + # Name-service deletion handling + name_service = commands.get('name_service') + if name_service: + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + cfg_name_service = have.get('name_service') + if cfg_name_service: + name_service_dict = {} + cfg_group = cfg_name_service.get('group') + cfg_netgroup = cfg_name_service.get('netgroup') + cfg_passwd = cfg_name_service.get('passwd') + cfg_shadow = cfg_name_service.get('shadow') + cfg_sudoers = cfg_name_service.get('sudoers') + + # Current SONiC behavior doesn't support single list item deletion + if group and group == cfg_group: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'group-method')) + name_service_dict['group'] = group + if netgroup and netgroup == cfg_netgroup: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'netgroup-method')) + name_service_dict['netgroup'] = netgroup + if passwd and passwd == cfg_passwd: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'passwd-method')) + name_service_dict['passwd'] = passwd + if shadow and shadow == cfg_shadow: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'shadow-method')) + name_service_dict['shadow'] = shadow + if sudoers and sudoers == cfg_sudoers: + requests.append(self.get_delete_request(AAA_NAME_SERVICE_PATH, 'sudoers-method')) + name_service_dict['sudoers'] = sudoers + if name_service_dict: + config_dict['name_service'] = name_service_dict + commands = config_dict + + return requests + + def get_delete_request(self, url, attr): + if attr: + url += '/%s' % (attr) + request = {'path': url, 'method': DELETE} return request - # Current SONiC code behavior for patch overwrites the OC authentication-method leaf-list - # This function serves as a workaround for the issue, allowing the user to append to the - # OC authentication-method leaf-list. def get_diff_aaa(self, want, have): - diff_cfg = {} - diff_authentication = {} - diff_data = {} + """AAA module requires custom diff method due to overwritting of list in SONiC""" + if not have: + return want - authentication = want.get('authentication', None) + cfg_dict = {} + # Authentication diff handling + authentication = want.get('authentication') if authentication: - data = authentication.get('data', None) - if data: - fail_through = data.get('fail_through', None) - local = data.get('local', None) - group = data.get('group', None) - - cfg_authentication = have.get('authentication', None) - if cfg_authentication: - cfg_data = cfg_authentication.get('data', None) - if cfg_data: - cfg_fail_through = cfg_data.get('fail_through', None) - cfg_local = cfg_data.get('local', None) - cfg_group = cfg_data.get('group', None) - - if fail_through is not None and fail_through != cfg_fail_through: - diff_data['fail_through'] = fail_through - if local and local != cfg_local: - diff_data['local'] = local - if group and group != cfg_group: - diff_data['group'] = group - - diff_local = diff_data.get('local', None) - diff_group = diff_data.get('group', None) - if diff_local and not diff_group and cfg_group: - diff_data['group'] = cfg_group - if diff_group and not diff_local and cfg_local: - diff_data['local'] = cfg_local - else: - if fail_through is not None: - diff_data['fail_through'] = fail_through - if local: - diff_data['local'] = local - if group: - diff_data['group'] = group - if diff_data: - diff_authentication['data'] = diff_data - diff_cfg['authentication'] = diff_authentication - - return diff_cfg + auth_method = authentication.get('auth_method') + console_auth_local = authentication.get('console_auth_local') + failthrough = authentication.get('failthrough') + + cfg_authentication = have.get('authentication') + if cfg_authentication: + authentication_dict = {} + cfg_auth_method = cfg_authentication.get('auth_method') + cfg_console_auth_local = cfg_authentication.get('console_auth_local') + cfg_failthrough = cfg_authentication.get('failthrough') + + if auth_method and auth_method != cfg_auth_method: + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None and console_auth_local != cfg_console_auth_local: + authentication_dict['console_auth_local'] = console_auth_local + if failthrough is not None and failthrough != cfg_failthrough: + authentication_dict['failthrough'] = failthrough + if authentication_dict: + cfg_dict['authentication'] = authentication_dict + else: + cfg_dict['authentication'] = authentication + + # Authorization diff handling + authorization = want.get('authorization') + if authorization: + commands_auth_method = authorization.get('commands_auth_method') + login_auth_method = authorization.get('login_auth_method') + + cfg_authorization = have.get('authorization') + if cfg_authorization: + authorization_dict = {} + cfg_commands_auth_method = cfg_authorization.get('commands_auth_method') + cfg_login_auth_method = cfg_authorization.get('login_auth_method') + + if commands_auth_method and commands_auth_method != cfg_commands_auth_method: + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method and login_auth_method != cfg_login_auth_method: + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + cfg_dict['authorization'] = authorization_dict + else: + cfg_dict['authorization'] = authorization + + # Name-service diff handling + name_service = want.get('name_service') + if name_service: + group = name_service.get('group') + netgroup = name_service.get('netgroup') + passwd = name_service.get('passwd') + shadow = name_service.get('shadow') + sudoers = name_service.get('sudoers') + + cfg_name_service = have.get('name_service') + if cfg_name_service: + name_service_dict = {} + cfg_group = cfg_name_service.get('group') + cfg_netgroup = cfg_name_service.get('netgroup') + cfg_passwd = cfg_name_service.get('passwd') + cfg_shadow = cfg_name_service.get('shadow') + cfg_sudoers = cfg_name_service.get('sudoers') + + if group and group != cfg_group: + name_service_dict['group'] = group + if netgroup and netgroup != cfg_netgroup: + name_service_dict['netgroup'] = netgroup + if passwd and passwd != cfg_passwd: + name_service_dict['passwd'] = passwd + if shadow and shadow != cfg_shadow: + name_service_dict['shadow'] = shadow + if sudoers and sudoers != cfg_sudoers: + name_service_dict['sudoers'] = sudoers + if name_service_dict: + cfg_dict['name_service'] = name_service_dict + else: + cfg_dict['name_service'] = name_service + + return cfg_dict + + def get_replaced_config(self, want, have): + config_dict = {} + authentication = want.get('authentication') + authorization = want.get('authorization') + name_service = want.get('name_service') + cfg_authentication = have.get('authentication') + cfg_authorization = have.get('authorization') + cfg_name_service = have.get('name_service') + + if authentication and cfg_authentication and authentication != cfg_authentication: + config_dict['authentication'] = cfg_authentication + if authorization and cfg_authorization and authorization != cfg_authorization: + config_dict['authorization'] = cfg_authorization + if name_service and cfg_name_service and name_service != cfg_name_service: + config_dict['name_service'] = cfg_name_service + + return config_dict + + def post_process_generated_config(self, data): + if data: + authentication = data.get('authentication') + authorization = data.get('authorization') + name_service = data.get('name_service') + + if authentication: + if 'auth_method' in authentication and not authentication['auth_method']: + data['authentication'].pop('auth_method') + if not data['authentication']: + data.pop('authentication') + if authorization: + if 'commands_auth_method' in authorization and not authorization['commands_auth_method']: + data['authorization'].pop('commands_auth_method') + if 'login_auth_method' in authorization and not authorization['login_auth_method']: + data['authorization'].pop('login_auth_method') + if not data['authorization']: + data.pop('authorization') + if name_service: + for method in ('group', 'netgroup', 'passwd', 'shadow', 'sudoers'): + if method in name_service and not name_service[method]: + data['name_service'].pop(method) + if not data['name_service']: + data.pop('name_service') diff --git a/plugins/module_utils/network/sonic/config/bgp/bgp.py b/plugins/module_utils/network/sonic/config/bgp/bgp.py index fa798d6f1..86a536b35 100644 --- a/plugins/module_utils/network/sonic/config/bgp/bgp.py +++ b/plugins/module_utils/network/sonic/config/bgp/bgp.py @@ -37,6 +37,10 @@ ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request from ansible.module_utils.connection import ConnectionError +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + convert_bgp_asn, + to_bgp_as_notation_request_type +) PATCH = 'patch' POST = 'post' @@ -165,6 +169,7 @@ def set_config(self, existing_bgp_facts): want = self._module.params['config'] if want: want = [remove_empties(conf) for conf in want] + convert_bgp_asn(want) else: want = [] @@ -384,6 +389,7 @@ def get_delete_specific_bgp_param_request(self, command, match): requests = [] router_id = command.get('router_id', None) + as_notation = command.get('as_notation', None) rt_delay = command.get('rt_delay', None) timers = command.get('timers', None) holdtime = None @@ -398,6 +404,10 @@ def get_delete_specific_bgp_param_request(self, command, match): url = '%s=%s/%s/global/config/router-id' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) requests.append({"path": url, "method": DELETE}) + if as_notation and match.get('as_notation', None): + url = '%s=%s/%s/global/config/as-notation' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) + requests.append({"path": url, "method": DELETE}) + if rt_delay and match.get('rt_delay', None): url = '%s=%s/%s/global/config/route-map-process-delay' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) requests.append({"path": url, "method": DELETE}) @@ -440,7 +450,8 @@ def get_delete_bgp_requests(self, commands, have, is_delete_all): if not match: continue # if there is specific parameters to delete then delete those alone - if cmd.get('router_id', None) or cmd.get('log_neighbor_changes', None) or cmd.get('bestpath', None) or cmd.get('rt_delay', None): + if cmd.get('router_id', None) or cmd.get('as_notation', None) or cmd.get('log_neighbor_changes', None) or cmd.get('bestpath', None) \ + or cmd.get('rt_delay', None): requests.extend(self.get_delete_specific_bgp_param_request(cmd, match)) else: # delete entire bgp @@ -552,16 +563,23 @@ def get_modify_max_med_requests(self, vrf_name, max_med): request = None method = PATCH payload = {} + config = {} on_startup_time = max_med.get('on_startup', {}).get('timer') on_startup_med = max_med.get('on_startup', {}).get('med_val') + if on_startup_time is not None: + config['time'] = on_startup_time + if on_startup_med is not None: + if on_startup_time is not None: + config['max-med-val'] = on_startup_med + else: + self._module.fail_json(msg='timer must be provided if med_val is present for max_med configuration.') + + if config: payload = { 'max-med': { - 'config': { - 'max-med-val': on_startup_med, - 'time': on_startup_time - } + 'config': config } } @@ -613,7 +631,7 @@ def get_modify_keepalive_request(self, vrf_name, keepalive_interval): return request - def get_new_bgp_request(self, vrf_name, as_val): + def get_new_bgp_request(self, vrf_name, as_val, as_notation): request = None url = None method = PATCH @@ -621,7 +639,9 @@ def get_new_bgp_request(self, vrf_name, as_val): cfg = {} if as_val: - as_cfg = {'config': {'as': float(as_val)}} + as_cfg = {'config': {'as': as_val.to_request_attr_fmt()}} + if as_notation: + as_cfg['config']['as-notation'] = to_bgp_as_notation_request_type(as_notation) global_cfg = {'global': as_cfg} cfg = {'bgp': global_cfg} cfg['name'] = "bgp" @@ -634,7 +654,7 @@ def get_new_bgp_request(self, vrf_name, as_val): return request - def get_modify_global_config_request(self, vrf_name, router_id, as_val, rt_delay): + def get_modify_global_config_request(self, vrf_name, router_id, as_val, rt_delay, as_notation): request = None method = PATCH payload = {} @@ -643,9 +663,11 @@ def get_modify_global_config_request(self, vrf_name, router_id, as_val, rt_delay if router_id: cfg['router-id'] = router_id if as_val: - cfg['as'] = float(as_val) + cfg['as'] = as_val.to_request_attr_fmt() if rt_delay: cfg['route-map-process-delay'] = rt_delay + if as_notation: + cfg['as-notation'] = to_bgp_as_notation_request_type(as_notation) if cfg: payload['openconfig-network-instance:config'] = cfg @@ -670,6 +692,7 @@ def get_modify_bgp_requests(self, commands, have): holdtime = None keepalive_interval = None rt_delay = None + as_notation = None if 'bgp_as' in conf: as_val = conf['bgp_as'] @@ -688,13 +711,15 @@ def get_modify_bgp_requests(self, commands, have): holdtime = conf['timers']['holdtime'] if 'keepalive_interval' in conf['timers']: keepalive_interval = conf['timers']['keepalive_interval'] + if 'as_notation' in conf: + as_notation = conf['as_notation'] if not any(cfg for cfg in have if cfg['vrf_name'] == vrf_name and (cfg['bgp_as'] == as_val)): - new_bgp_req = self.get_new_bgp_request(vrf_name, as_val) + new_bgp_req = self.get_new_bgp_request(vrf_name, as_val, as_notation) if new_bgp_req: requests.append(new_bgp_req) - global_req = self.get_modify_global_config_request(vrf_name, router_id, as_val, rt_delay) + global_req = self.get_modify_global_config_request(vrf_name, router_id, as_val, rt_delay, as_notation) if global_req: requests.append(global_req) @@ -754,6 +779,9 @@ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state if conf.get('router_id') and not match_cfg.get('router_id'): command['router_id'] = conf['router_id'] + if conf.get('as_notation') and not match_cfg.get('as_notation'): + command['as_notation'] = conf['as_notation'] + if conf.get('rt_delay') and match_cfg.get('rt_delay') is None: command['rt_delay'] = conf['rt_delay'] diff --git a/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py b/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py index 19ef9db0e..84850a084 100644 --- a/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py +++ b/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -46,6 +46,7 @@ remove_void_config ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + convert_bgp_asn, validate_bgps ) from ansible.module_utils.connection import ConnectionError @@ -57,14 +58,16 @@ {'afis': {'afi': '', 'safi': ''}}, {'redistribute': {'protocol': ''}}, {'route_advertise_list': {'advertise_afi': ''}}, - {'vnis': {'vni_number': ''}} + {'vnis': {'vni_number': ''}}, + {'aggregate_address_config': {'prefix': ''}} ] TEST_KEYS_sort_config = [ {'config': {'__test_keys': ('bgp_as', 'vrf_name')}}, {'afis': {'__test_keys': ('afi', 'safi')}}, {'redistribute': {'__test_keys': ('protocol',)}}, {'route_advertise_list': {'__test_keys': ('advertise_afi',)}}, - {'vnis': {'__test_keys': ('vni_number',)}} + {'vnis': {'__test_keys': ('vni_number',)}}, + {'aggregate_address_config': {'__test_keys': ('prefix',)}} ] is_delete_all = False @@ -97,7 +100,9 @@ def __derive_bgp_af_delete_op(key_set, command, exist_conf): {'route_advertise_list': {'advertise_afi': '', '__delete_op': __derive_bgp_af_sub_config_delete_op}}, {'vnis': {'vni_number': '', - '__delete_op': __derive_bgp_af_sub_config_delete_op}} + '__delete_op': __derive_bgp_af_sub_config_delete_op}}, + {'aggregate_address_config': {'prefix': '', + '__delete_op': __derive_bgp_af_sub_config_delete_op}} ] @@ -214,6 +219,7 @@ def set_config(self, existing_bgp_af_facts): state = self._module.params['state'] want = self._module.params['config'] if want: + convert_bgp_asn(want) # In state deleted, specific empty parameters are supported if state != 'deleted': want = [remove_empties(conf) for conf in want] @@ -537,6 +543,36 @@ def get_modify_dampening_request(self, vrf_name, conf_afi, conf_safi, conf_dampe request = {"path": url, "method": PATCH, "data": damp_payload} return request + def get_modify_aggregate_request(self, vrf_name, conf_afi, conf_safi, conf_aggregate): + request = None + + if conf_aggregate: + addr_list = [] + for addr in conf_aggregate: + config_dict = {} + prefix = addr.get('prefix') + as_set = addr.get('as_set') + policy_name = addr.get('policy_name') + summary_only = addr.get('summary_only') + + if as_set is not None: + config_dict['as-set'] = as_set + if policy_name: + config_dict['policy-name'] = policy_name + if summary_only is not None: + config_dict['summary-only'] = summary_only + if prefix: + config_dict['prefix'] = prefix + addr_list.append({'prefix': prefix, 'config': config_dict}) + if addr_list: + payload = {'openconfig-network-instance:aggregate-address-config': {'aggregate-address': addr_list}} + afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper() + url = '%s=%s/%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) + url += '%s=%s/aggregate-address-config' % (self.afi_safi_path, afi_safi) + request = {'path': url, 'method': PATCH, 'data': payload} + + return request + def get_modify_import_request(self, vrf_name, conf_afi, conf_safi, conf_import): request = None url = '{0}={1}/{2}/{3}={4}_{5}/openconfig-bgp-ext:import-network-instance'.format(self.network_instance_path, vrf_name, self.protocol_bgp_path, @@ -581,6 +617,11 @@ def get_modify_single_af_request(self, vrf_name, conf_afi, conf_safi, conf_addr_ import_req = self.get_modify_import_request(vrf_name, conf_afi, conf_safi, conf_import) if import_req: requests.append(import_req) + conf_aggregate = conf_addr_fam.get('aggregate_address_config') + if conf_aggregate: + request = self.get_modify_aggregate_request(vrf_name, conf_afi, conf_safi, conf_aggregate) + if request: + requests.append(request) elif conf_afi == "l2vpn" and conf_safi == 'evpn': cfg_req = self.get_modify_evpn_adv_cfg_request(vrf_name, conf_afi, conf_safi, conf_addr_fam) vni_req = self.get_modify_evpn_vnis_request(vrf_name, conf_afi, conf_safi, conf_addr_fam) @@ -654,7 +695,8 @@ def get_modify_requests(self, conf, match, vrf_name): conf_max_path = conf_addr_fam.get('max_path', None) conf_network = conf_addr_fam.get('network', []) conf_import = conf_addr_fam.get('import', None) - if not conf_redis_arr and not conf_max_path and not conf_network and not conf_import: + conf_aggregate = conf_addr_fam.get('aggregate_address_config') + if not conf_redis_arr and not conf_max_path and not conf_network and not conf_import and not conf_aggregate: continue url = "%s=%s/table-connections" % (self.network_instance_path, vrf_name) @@ -704,6 +746,10 @@ def get_modify_requests(self, conf, match, vrf_name): import_req = self.get_modify_import_request(vrf_name, conf_afi, conf_safi, conf_import) if import_req: requests.append(import_req) + if conf_aggregate: + aggregate_req = self.get_modify_aggregate_request(vrf_name, conf_afi, conf_safi, conf_aggregate) + if aggregate_req: + requests.append(aggregate_req) return requests @@ -852,6 +898,38 @@ def get_delete_vnis_requests(self, vrf_name, conf_afi, conf_safi, conf_vnis, is_ return requests + def get_delete_aggregate_requests(self, vrf_name, conf_afi, conf_safi, conf_aggregate, is_delete_all, mat_aggregate): + requests = [] + + if is_delete_all: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, None, None)) + return requests + + mat_addr_dict = {mat_addr.get('prefix'): mat_addr for mat_addr in mat_aggregate} + for addr in conf_aggregate: + prefix = addr.get('prefix') + mat_addr = mat_addr_dict.get(prefix) + + if mat_addr is None: + continue + as_set = addr.get('as_set') + policy_name = addr.get('policy_name') + summary_only = addr.get('summary_only') + mat_as_set = mat_addr.get('as_set') + mat_policy_name = mat_addr.get('policy_name') + mat_summary_only = mat_addr.get('summary_only') + + if as_set is not None and as_set == mat_as_set: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, prefix, 'as-set')) + if policy_name and policy_name == mat_policy_name: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, prefix, 'policy-name')) + if summary_only is not None and summary_only == mat_summary_only: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, prefix, 'summary-only')) + if as_set is None and not policy_name and summary_only is None: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, prefix, None)) + + return requests + def get_delete_dampening_request(self, vrf_name, conf_afi, conf_safi): afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper() url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) @@ -869,6 +947,20 @@ def get_delete_address_family_request(self, vrf_name, conf_afi, conf_safi): return request + def get_delete_aggregate_attr(self, vrf_name, conf_afi, conf_safi, prefix, attr): + request = None + + afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper() + url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) + url += '/%s=%s/aggregate-address-config' % (self.afi_safi_path, afi_safi) + if prefix: + url += '/aggregate-address=%s' % (prefix.replace('/', '%2F')) + if attr: + url += '/config/%s' % (attr) + request = {"path": url, "method": DELETE} + + return request + def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): requests = [] vrf_name = conf['vrf_name'] @@ -909,6 +1001,8 @@ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): conf_rt_out = conf_addr_fam.get('rt_out', []) conf_vnis = conf_addr_fam.get('vnis', []) conf_import = conf_addr_fam.get('import', None) + conf_aggregate = conf_addr_fam.get('aggregate_address_config') + if is_delete_all: if conf_adv_pip_ip: requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-ip')) @@ -942,6 +1036,8 @@ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, conf_vnis, is_delete_all, None)) if conf_import: requests.extend(self.get_delete_import_requests(vrf_name, conf_afi, conf_safi, conf_import, is_delete_all, None)) + if conf_aggregate: + requests.append(self.get_delete_aggregate_attr(vrf_name, conf_afi, conf_safi, None, None)) addr_family_del_req = self.get_delete_address_family_request(vrf_name, conf_afi, conf_safi) if addr_family_del_req: requests.append(addr_family_del_req) @@ -971,10 +1067,12 @@ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): mat_rt_out = match_addr_fam.get('rt_out', []) mat_vnis = match_addr_fam.get('vnis', []) mat_import = match_addr_fam.get('import', None) + mat_aggregate = match_addr_fam.get('aggregate_address_config') if (conf_adv_pip is None and not conf_adv_pip_ip and not conf_adv_pip_peer_ip and conf_adv_svi_ip is None and not conf_import and conf_adv_all_vni is None and not conf_redis_arr and conf_adv_default_gw is None and not conf_max_path and conf_dampening is - None and not conf_network and not conf_route_adv_list and not conf_rd and not conf_rt_in and not conf_rt_out and not conf_vnis): + None and not conf_network and not conf_route_adv_list and not conf_rd and not conf_rt_in and not conf_rt_out and not conf_vnis + and not conf_aggregate): if mat_advt_pip_ip: requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-ip')) if mat_advt_pip_peer_ip: @@ -1008,6 +1106,9 @@ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, mat_vnis, is_delete_all, mat_vnis)) if mat_import: requests.extend(self.get_delete_import_requests(vrf_name, conf_afi, conf_safi, mat_import, is_delete_all, mat_import)) + if mat_aggregate: + requests.extend(self.get_delete_aggregate_requests(vrf_name, conf_afi, conf_safi, mat_aggregate, is_delete_all, + mat_aggregate)) addr_family_del_req = self.get_delete_address_family_request(vrf_name, conf_afi, conf_safi) if addr_family_del_req: requests.append(addr_family_del_req) @@ -1051,6 +1152,8 @@ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None): requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, conf_vnis, is_delete_all, mat_vnis)) if conf_import and mat_import: requests.extend(self.get_delete_import_requests(vrf_name, conf_afi, conf_safi, conf_import, is_delete_all, mat_import)) + if conf_aggregate and mat_aggregate: + requests.extend(self.get_delete_aggregate_requests(vrf_name, conf_afi, conf_safi, conf_aggregate, is_delete_all, mat_aggregate)) break return requests @@ -1248,11 +1351,12 @@ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state safi = afi_conf['safi'] match_afi_cfg = next((afi_cfg for afi_cfg in match_afi_list if afi_cfg['afi'] == afi and afi_cfg['safi'] == safi), None) - # Delete address-families that are not specified + # Delete address-families that are not specified in overridden if not match_afi_cfg: - afi_command_list.append(afi_conf) - requests.extend(self.get_delete_single_bgp_af_request({'bgp_as': as_val, 'vrf_name': vrf_name, 'address_family': {'afis': [afi_conf]}}, - True)) + if state == 'overridden': + afi_command_list.append(afi_conf) + requests.extend(self.get_delete_single_bgp_af_request({'bgp_as': as_val, 'vrf_name': vrf_name, 'address_family': {'afis': [afi_conf]}}, + True)) continue if afi == 'ipv4' and safi == 'unicast': @@ -1398,6 +1502,31 @@ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state afi_command['import'] = import_vrf_command requests.extend(self.get_delete_import_requests(vrf_name, afi, safi, afi_command['import'], False, afi_command['import'])) + if afi_conf.get('aggregate_address_config'): + aggregate_cmd_list = [] + match_aggregate = match_afi_cfg.get('aggregate_address_config') + if match_aggregate: + match_addr_dict = {mat_addr.get('prefix'): mat_addr for mat_addr in match_aggregate} + for addr in afi_conf['aggregate_address_config']: + prefix = addr['prefix'] + match_addr = match_addr_dict.get(prefix) + + if not match_addr: + aggregate_cmd_list.append(addr) + requests.append(self.get_delete_aggregate_attr(vrf_name, afi, safi, prefix, None)) + else: + aggregate_cmd = {} + for option in ('as_set', 'policy_name', 'summary_only'): + if addr.get(option) is not None and match_addr.get(option) is None: + aggregate_cmd[option] = addr[option] + attr = option.replace('_', '-') + requests.append(self.get_delete_aggregate_attr(vrf_name, afi, safi, prefix, attr)) + if aggregate_cmd: + aggregate_cmd['prefix'] = prefix + aggregate_cmd_list.append(aggregate_cmd) + if aggregate_cmd_list: + afi_command['aggregate_address_config'] = aggregate_cmd_list + if afi_command: afi_command['afi'] = afi afi_command['safi'] = safi diff --git a/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py b/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py index a0f1a65f9..8cc0bd0d8 100644 --- a/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py +++ b/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat +# © Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -13,6 +13,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) @@ -28,7 +29,7 @@ update_states, get_diff, remove_matching_defaults, - add_config_defaults, + remove_empties, remove_empties_from_list ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( @@ -42,16 +43,14 @@ remove_void_config ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + convert_bgp_asn, validate_bgps, normalize_neighbors_interface_name, get_ip_afi_cfg_payload, get_prefix_limit_payload ) -from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request from ansible.module_utils.connection import ConnectionError -from copy import deepcopy - PATCH = 'patch' DELETE = 'delete' @@ -62,7 +61,7 @@ {'afis': {'afi': '', 'safi': ''}}, ] -default_entries = [ +DEFAULT_ENTRIES = [ [ {'name': 'peer_group'}, {'name': 'timers'}, @@ -80,7 +79,7 @@ ], [ {'name': 'peer_group'}, - {'name': 'advertisement_interval', 'default': 30} + {'name': 'advertisement_interval', 'default': 0} ], [ {'name': 'peer_group'}, @@ -96,6 +95,16 @@ {'name': 'peer_group'}, {'name': 'passive', 'default': False} ], + [ + {'name': 'peer_group'}, + {'name': 'local_as'}, + {'name': 'no_prepend', 'default': False} + ], + [ + {'name': 'peer_group'}, + {'name': 'local_as'}, + {'name': 'replace_as', 'default': False} + ], [ {'name': 'peer_group'}, {'name': 'address_family'}, @@ -133,7 +142,7 @@ ], [ {'name': 'neighbors'}, - {'name': 'advertisement_interval', 'default': 30} + {'name': 'advertisement_interval', 'default': 0} ], [ {'name': 'neighbors'}, @@ -149,6 +158,16 @@ {'name': 'neighbors'}, {'name': 'passive', 'default': False} ], + [ + {'name': 'neighbors'}, + {'name': 'local_as'}, + {'name': 'no_prepend', 'default': False} + ], + [ + {'name': 'neighbors'}, + {'name': 'local_as'}, + {'name': 'replace_as', 'default': False} + ] ] is_delete_all = False @@ -241,30 +260,34 @@ def execute_module(self): except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) result['changed'] = True - result['commands'] = commands - changed_bgp_facts = self.get_bgp_neighbors_facts() + existing_bgp_facts = sort_config(existing_bgp_facts, TEST_KEYS_sort_config) result['before'] = existing_bgp_facts - if result['changed']: - result['after'] = changed_bgp_facts - - new_config = changed_bgp_facts old_config = existing_bgp_facts + if self._module.check_mode: result.pop('after', None) - new_config = get_new_config(commands, existing_bgp_facts, + old_config = self.pre_process_generated_config(commands, deepcopy(existing_bgp_facts)) + new_config = get_new_config(commands, old_config, TEST_KEYS_generate_config) new_config = self.post_process_generated_config(new_config) - old_config = remove_empties_from_list(old_config) + new_config = remove_empties_from_list(new_config) + new_config = sort_config(new_config, TEST_KEYS_sort_config) result['after(generated)'] = new_config + else: + changed_bgp_facts = self.get_bgp_neighbors_facts() + new_config = changed_bgp_facts + new_config = sort_config(new_config, TEST_KEYS_sort_config) + if result['changed']: + result['after'] = new_config if self._module._diff: new_config = sort_config(new_config, TEST_KEYS_sort_config) - old_config = sort_config(old_config, TEST_KEYS_sort_config) result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + result['commands'] = commands result['warnings'] = warnings return result @@ -277,6 +300,7 @@ def set_config(self, existing_bgp_facts): to the desired configuration """ want = self._module.params['config'] + convert_bgp_asn(want) normalize_neighbors_interface_name(want, self._module) have = existing_bgp_facts resp = self.set_state(want, have) @@ -295,15 +319,52 @@ def set_state(self, want, have): requests = [] state = self._module.params['state'] - diff = get_diff(want, have, TEST_KEYS) - - if state == 'deleted': - commands, requests = self._state_deleted(want, have, diff) + if state == 'replaced' or state == 'overridden': + commands, requests = self._state_replaced_or_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) elif state == 'merged': - commands, requests = self._state_merged(want, have, diff) + commands, requests = self._state_merged(want, have) + return commands, requests + + def _state_replaced_or_overridden(self, want, have): + """ The command generator when state is replaced or overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + new_have = deepcopy(have) + new_want = deepcopy(want) + for cmd in new_want: + neighbors = cmd.get('neighbors', []) + peergroup = cmd.get('peer_group', []) + # Set passive to false if not specified for a new neighbor/peer-group + if neighbors: + for nbr in neighbors: + if nbr.get('passive') is None: + nbr['passive'] = False + if peergroup: + for pg in peergroup: + if pg.get('passive') is None: + pg['passive'] = False + new_want = remove_empties_from_list(new_want) + want_skeleton = self._get_skeleton_keys(new_want) + add_config, del_config = self._get_replaced_overridden_config(new_want, new_have, want_skeleton) + if del_config: + del_cmd, del_requests = self.get_delete_commands_requests_for_deleted(del_config, new_have) + if del_requests: + requests.extend(del_requests) + commands.extend(update_states(del_cmd, "deleted")) + if add_config: + mod_requests = self.get_modify_bgp_requests(add_config, have) + if len(mod_requests) > 0: + requests.extend(mod_requests) + commands.extend(update_states(add_config, self._module.params['state'])) return commands, requests - def _state_merged(self, want, have, diff): + def _state_merged(self, want, have): """ The command generator when state is merged :param want: the additive configuration as a dictionary @@ -312,18 +373,45 @@ def _state_merged(self, want, have, diff): :returns: the commands necessary to merge the provided into the current configuration """ - commands = [] requests = [] - commands = diff + commands = get_diff(want, have, TEST_KEYS) validate_bgps(self._module, commands, have) + for cmd in commands: + neighbors = cmd.get('neighbors', []) + peergroup = cmd.get('peer_group', []) + match = next((item for item in have if (item['vrf_name'] == cmd['vrf_name'] and item['bgp_as'] == cmd['bgp_as'])), None) + if match: + match_neighbors = match.get('neighbors', []) + match_peergroup = match.get('peer_group', []) + if neighbors: + for nbr in neighbors: + match_nbr = next((item for item in match_neighbors if item['neighbor'] == nbr['neighbor']), None) + if nbr.get('passive') is None: + if not match_nbr: + nbr['passive'] = False + if peergroup: + for pg in peergroup: + match_pg = next((item for item in match_peergroup if item['name'] == pg['name']), None) + if pg.get('passive') is None: + if not match_pg: + pg['passive'] = False + else: + # Set passive to false if not specified for a new neighbor/peer-group + for nbr in neighbors: + if nbr.get('passive') is None: + nbr['passive'] = False + for pg in peergroup: + if pg.get('passive') is None: + pg['passive'] = False requests = self.get_modify_bgp_requests(commands, have) if commands and len(requests) > 0: commands = update_states(commands, "merged") else: commands = [] + return commands, requests - def _state_deleted(self, want, have, diff): + def _state_deleted(self, want, have): """ The command generator when state is deleted :param want: the objects from which the configuration should be removed @@ -332,26 +420,113 @@ def _state_deleted(self, want, have, diff): :returns: the commands necessary to remove the current configuration of the provided objects """ + global is_delete_all is_delete_all = False + if not want: - is_delete_all = True - if is_delete_all: - commands = have new_have = have + new_want = want else: new_have = deepcopy(have) - for default_entry in default_entries: + new_want = deepcopy(want) + for default_entry in DEFAULT_ENTRIES: remove_matching_defaults(new_have, default_entry) - d_diff = get_diff(want, new_have, TEST_KEYS, is_skeleton=True) - delete_diff = get_diff(want, d_diff, TEST_KEYS, is_skeleton=True) - commands = delete_diff - requests = self.get_delete_bgp_neighbor_requests(commands, new_have, want, is_delete_all) + remove_matching_defaults(new_want, default_entry) + commands, requests = self.get_delete_commands_requests_for_deleted(new_want, new_have) if commands and len(requests) > 0: commands = update_states(commands, "deleted") else: commands = [] + + return commands, requests + + def _get_replaced_overridden_config(self, want, have, want_skeleton): + add_config, del_config = [], [] + + diff1 = get_diff(want, have, TEST_KEYS) + for default_entry in DEFAULT_ENTRIES: + remove_matching_defaults(have, default_entry) + remove_matching_defaults(want, default_entry) + want = remove_empties_from_list(want) + have = remove_empties_from_list(have) + diff2 = get_diff(have, want, TEST_KEYS) + state = self._module.params['state'] + + add_config = diff1 + for cmd in diff2: + del_cfg = {} + vrf_name = cmd.get('vrf_name') + bgp_as = cmd.get('bgp_as') + match_neighbors = [] + match_peergroup = [] + if bgp_as in want_skeleton and vrf_name in want_skeleton[bgp_as]: + match_neighbors = want_skeleton[bgp_as][vrf_name].get('neighbors', []) + match_peergroup = want_skeleton[bgp_as][vrf_name].get('peer_group', []) + + neighbors = cmd.get('neighbors', []) + for nbr in neighbors: + neighbor_name = nbr.get('neighbor') + match_nbr = True if neighbor_name in match_neighbors else False + if match_nbr: + del_cfg.setdefault('neighbors', []).append(nbr) + elif state == 'overridden': + del_cfg.setdefault('neighbors', []).append({'neighbor': neighbor_name}) + peergroup = cmd.get('peer_group', []) + for pg in peergroup: + name = pg.get('name') + match_pg = True if name in match_peergroup else False + if match_pg: + del_cfg.setdefault('peer_group', []).append(self._get_delete_pg_commands(pg, match_peergroup[name])) + elif state == 'overridden': + del_cfg.setdefault('peer_group', []).append({'name': name}) + + if del_cfg: + del_cfg['bgp_as'] = bgp_as + del_cfg['vrf_name'] = vrf_name + del_config.append(del_cfg) + return add_config, del_config + + def get_delete_commands_requests_for_deleted(self, want, have): + commands, requests = [], [] + if not have: + return commands, requests + + if not want: + commands = remove_empties_from_list(have) + requests = self.get_delete_all_bgp_neighbor_peergroup_requests(commands) + return commands, requests + + for conf in want: + vrf_name = conf.get('vrf_name') + bgp_as = conf.get('bgp_as') + cmd, requests_nbr_del, requests_pg_del = {}, [], [] + neighbors = conf.get('neighbors') + peer_group = conf.get('peer_group') + have_conf = next((cfg for cfg in have if vrf_name == cfg['vrf_name'] and bgp_as == cfg['bgp_as']), None) + + if have_conf: + have_conf = remove_empties(have_conf) + if have_conf.get('neighbors') and neighbors: + commands_del = [] + commands_del, requests_nbr_del = self.get_delete_bgp_neighbor_commands_requests(vrf_name, neighbors, have_conf['neighbors']) + if commands_del and len(requests_nbr_del) > 0: + cmd['neighbors'] = commands_del + + if have_conf.get('peer_group') and peer_group: + commands_del = [] + commands_del, requests_pg_del = self.get_delete_bgp_peergroup_commands_requests(vrf_name, peer_group, have_conf['peer_group']) + if commands_del and len(requests_pg_del) > 0: + cmd['peer_group'] = commands_del + + if cmd: + cmd['bgp_as'] = bgp_as + cmd['vrf_name'] = vrf_name + commands.append(cmd) + requests.extend(requests_nbr_del) + requests.extend(requests_pg_del) + return commands, requests def build_bgp_peer_groups_payload(self, cmd, have, bgp_as, vrf_name): @@ -359,335 +534,242 @@ def build_bgp_peer_groups_payload(self, cmd, have, bgp_as, vrf_name): bgp_peer_group_list = [] for peer_group in cmd: if peer_group: - bgp_peer_group = {} - peer_group_cfg = {} - tmp_bfd = {} - tmp_ebgp = {} - tmp_timers = {} - tmp_capability = {} - tmp_remote = {} - tmp_transport = {} + bgp_peer_group, peer_group_cfg = {}, {} + tmp_bfd, tmp_ebgp, tmp_capability = {}, {}, {} + tmp_transport, tmp_timers, tmp_remote = {}, {}, {} afi = [] - if peer_group.get('name', None) is not None: - peer_group_cfg.update({'peer-group-name': peer_group['name']}) - bgp_peer_group.update({'peer-group-name': peer_group['name']}) - if peer_group.get('bfd', None) is not None: - if peer_group['bfd'].get('enabled', None) is not None: - tmp_bfd.update({'enabled': peer_group['bfd']['enabled']}) - if peer_group['bfd'].get('check_failure', None) is not None: - tmp_bfd.update({'check-control-plane-failure': peer_group['bfd']['check_failure']}) - if peer_group['bfd'].get('profile', None) is not None: - tmp_bfd.update({'bfd-profile': peer_group['bfd']['profile']}) - if peer_group.get('auth_pwd', None) is not None: - if (peer_group['auth_pwd'].get('pwd', None) is not None and - peer_group['auth_pwd'].get('encrypted', None) is not None): + + self.update_dict(peer_group, peer_group_cfg, 'name', 'peer-group-name') + self.update_dict(peer_group, bgp_peer_group, 'name', 'peer-group-name') + + if peer_group.get('bfd') is not None: + self.update_dict(peer_group['bfd'], tmp_bfd, 'enabled', 'enabled') + self.update_dict(peer_group['bfd'], tmp_bfd, 'check_failure', 'check-control-plane-failure') + self.update_dict(peer_group['bfd'], tmp_bfd, 'profile', 'bfd-profile') + + if peer_group.get('auth_pwd') is not None: + if (peer_group['auth_pwd'].get('pwd') is not None and peer_group['auth_pwd'].get('encrypted') is not None): bgp_peer_group.update({'auth-password': {'config': {'password': peer_group['auth_pwd']['pwd'], 'encrypted': peer_group['auth_pwd']['encrypted']}}}) - if peer_group.get('ebgp_multihop', None) is not None: - if peer_group['ebgp_multihop'].get('enabled', None) is not None: - tmp_ebgp.update({'enabled': peer_group['ebgp_multihop']['enabled']}) - if peer_group['ebgp_multihop'].get('multihop_ttl', None) is not None: - tmp_ebgp.update({'multihop-ttl': peer_group['ebgp_multihop']['multihop_ttl']}) - if peer_group.get('timers', None) is not None: - if peer_group['timers'].get('holdtime', None) is not None: - tmp_timers.update({'hold-time': peer_group['timers']['holdtime']}) - if peer_group['timers'].get('keepalive', None) is not None: - tmp_timers.update({'keepalive-interval': peer_group['timers']['keepalive']}) - if peer_group['timers'].get('connect_retry', None) is not None: - tmp_timers.update({'connect-retry': peer_group['timers']['connect_retry']}) - if peer_group.get('capability', None) is not None: - if peer_group['capability'].get('dynamic', None) is not None: - tmp_capability.update({'capability-dynamic': peer_group['capability']['dynamic']}) - if peer_group['capability'].get('extended_nexthop', None) is not None: - tmp_capability.update({'capability-extended-nexthop': peer_group['capability']['extended_nexthop']}) - if peer_group.get('pg_description', None) is not None: - peer_group_cfg.update({'description': peer_group['pg_description']}) - if peer_group.get('disable_connected_check', None) is not None: - peer_group_cfg.update({'disable-ebgp-connected-route-check': peer_group['disable_connected_check']}) - if peer_group.get('dont_negotiate_capability', None) is not None: - peer_group_cfg.update({'dont-negotiate-capability': peer_group['dont_negotiate_capability']}) - if peer_group.get('enforce_first_as', None) is not None: - peer_group_cfg.update({'enforce-first-as': peer_group['enforce_first_as']}) - if peer_group.get('enforce_multihop', None) is not None: - peer_group_cfg.update({'enforce-multihop': peer_group['enforce_multihop']}) - if peer_group.get('override_capability', None) is not None: - peer_group_cfg.update({'override-capability': peer_group['override_capability']}) - if peer_group.get('shutdown_msg', None) is not None: - peer_group_cfg.update({'shutdown-message': peer_group['shutdown_msg']}) - if peer_group.get('solo', None) is not None: - peer_group_cfg.update({'solo-peer': peer_group['solo']}) - if peer_group.get('strict_capability_match', None) is not None: - peer_group_cfg.update({'strict-capability-match': peer_group['strict_capability_match']}) - if peer_group.get('ttl_security', None) is not None: - peer_group_cfg.update({'ttl-security-hops': peer_group['ttl_security']}) - if peer_group.get('local_as', None) is not None: - if peer_group['local_as'].get('as', None) is not None: - peer_group_cfg.update({'local-as': peer_group['local_as']['as']}) - if peer_group['local_as'].get('no_prepend', None) is not None: - peer_group_cfg.update({'local-as-no-prepend': peer_group['local_as']['no_prepend']}) - if peer_group['local_as'].get('replace_as', None) is not None: - peer_group_cfg.update({'local-as-replace-as': peer_group['local_as']['replace_as']}) - if peer_group.get('local_address', None) is not None: - tmp_transport.update({'local-address': peer_group['local_address']}) - if peer_group.get('passive', None) is not None: - tmp_transport.update({'passive-mode': peer_group['passive']}) - if peer_group.get('advertisement_interval', None) is not None: - tmp_timers.update({'minimum-advertisement-interval': peer_group['advertisement_interval']}) - if peer_group.get('remote_as', None) is not None: + + if peer_group.get('ebgp_multihop') is not None: + self.update_dict(peer_group['ebgp_multihop'], tmp_ebgp, 'enabled', 'enabled') + self.update_dict(peer_group['ebgp_multihop'], tmp_ebgp, 'multihop_ttl', 'multihop-ttl') + + if peer_group.get('timers') is not None: + self.update_dict(peer_group['timers'], tmp_timers, 'holdtime', 'hold-time') + self.update_dict(peer_group['timers'], tmp_timers, 'keepalive', 'keepalive-interval') + self.update_dict(peer_group['timers'], tmp_timers, 'connect_retry', 'connect-retry') + + if peer_group.get('capability') is not None: + self.update_dict(peer_group['capability'], tmp_capability, 'dynamic', 'capability-dynamic') + self.update_dict(peer_group['capability'], tmp_capability, 'extended_nexthop', 'capability-extended-nexthop') + + self.update_dict(peer_group, peer_group_cfg, 'pg_description', 'description') + self.update_dict(peer_group, peer_group_cfg, 'disable_connected_check', 'disable-ebgp-connected-route-check') + self.update_dict(peer_group, peer_group_cfg, 'dont_negotiate_capability', 'dont-negotiate-capability') + self.update_dict(peer_group, peer_group_cfg, 'enforce_first_as', 'enforce-first-as') + self.update_dict(peer_group, peer_group_cfg, 'enforce_multihop', 'enforce-multihop') + self.update_dict(peer_group, peer_group_cfg, 'override_capability', 'override-capability') + self.update_dict(peer_group, peer_group_cfg, 'shutdown_msg', 'shutdown-message') + self.update_dict(peer_group, peer_group_cfg, 'solo', 'solo-peer') + self.update_dict(peer_group, peer_group_cfg, 'strict_capability_match', 'strict-capability-match') + self.update_dict(peer_group, peer_group_cfg, 'ttl_security', 'ttl-security-hops') + + if peer_group.get('local_as') is not None: + self.update_dict(peer_group['local_as'], peer_group_cfg, 'as', 'local-as') + self.update_dict(peer_group['local_as'], peer_group_cfg, 'no_prepend', 'local-as-no-prepend') + self.update_dict(peer_group['local_as'], peer_group_cfg, 'replace_as', 'local-as-replace-as') + + self.update_dict(peer_group, tmp_transport, 'local_address', 'local-address') + self.update_dict(peer_group, tmp_transport, 'passive', 'passive-mode') + self.update_dict(peer_group, tmp_timers, 'advertisement_interval', 'minimum-advertisement-interval') + + if peer_group.get('remote_as') is not None: have_nei = self.find_pg(have, bgp_as, vrf_name, peer_group) - if peer_group['remote_as'].get('peer_as', None) is not None: + if peer_group['remote_as'].get('peer_as') is not None: if have_nei: - if have_nei.get("remote_as", None) is not None: - if have_nei["remote_as"].get("peer_type", None) is not None: - del_nei = {} - del_nei.update({'name': have_nei['name']}) - del_nei.update({'remote_as': have_nei['remote_as']}) - requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei)) - tmp_remote.update({'peer-as': peer_group['remote_as']['peer_as']}) - if peer_group['remote_as'].get('peer_type', None) is not None: + if have_nei.get("remote_as") is not None: + if have_nei["remote_as"].get("peer_type") is not None: + del_nei = {'name': have_nei['name'], 'remote_as': have_nei['remote_as']} + requests.extend(self.get_delete_specific_peergroup_param_requests(vrf_name, del_nei)) + tmp_remote.update({'peer-as': peer_group['remote_as']['peer_as'].to_request_attr_fmt()}) + if peer_group['remote_as'].get('peer_type') is not None: if have_nei: - if have_nei.get("remote_as", None) is not None: - if have_nei["remote_as"].get("peer_as", None) is not None: - del_nei = {} - del_nei.update({'name': have_nei['name']}) - del_nei.update({'remote_as': have_nei['remote_as']}) - requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei)) + if have_nei.get("remote_as") is not None: + if have_nei["remote_as"].get("peer_as") is not None: + del_nei = {'name': have_nei['name'], 'remote_as': have_nei['remote_as']} + requests.extend(self.get_delete_specific_peergroup_param_requests(vrf_name, del_nei)) tmp_remote.update({'peer-type': peer_group['remote_as']['peer_type'].upper()}) - if peer_group.get('address_family', None) is not None: - if peer_group['address_family'].get('afis', None) is not None: - for each in peer_group['address_family']['afis']: - samp = {} - afi_safi_cfg = {} - pfx_lmt_cfg = {} - pfx_lst_cfg = {} - ip_dict = {} - if each.get('afi', None) is not None and each.get('safi', None) is not None: - afi_safi = each['afi'].upper() + "_" + each['safi'].upper() - if afi_safi is not None: - afi_safi_name = 'openconfig-bgp-types:' + afi_safi - if afi_safi_name is not None: - samp.update({'afi-safi-name': afi_safi_name}) - samp.update({'config': {'afi-safi-name': afi_safi_name}}) - if each.get('prefix_limit', None) is not None: + + if peer_group.get('address_family') is not None: + if peer_group['address_family'].get('afis') is not None: + for each in peer_group['address_family'].get('afis', []): + samp, pfx_lmt_cfg, pfx_lst_cfg, ip_dict = {}, {}, {}, {} + afi_safi = each.get('afi', '').upper() + "_" + each.get('safi', '').upper() + afi_safi_name = 'openconfig-bgp-types:' + afi_safi + samp.update({'afi-safi-name': afi_safi_name, 'config': {'afi-safi-name': afi_safi_name}}) + if each.get('prefix_limit'): pfx_lmt_cfg = get_prefix_limit_payload(each['prefix_limit']) - if pfx_lmt_cfg and afi_safi == 'L2VPN_EVPN': - self._module.fail_json('Prefix limit configuration not supported for l2vpn evpn') - else: - if each.get('ip_afi', None) is not None: - afi_safi_cfg = get_ip_afi_cfg_payload(each['ip_afi']) - if afi_safi_cfg: - ip_dict.update({'config': afi_safi_cfg}) - if pfx_lmt_cfg: - ip_dict.update({'prefix-limit': {'config': pfx_lmt_cfg}}) - if ip_dict and afi_safi == 'IPV4_UNICAST': - samp.update({'ipv4-unicast': ip_dict}) - elif ip_dict and afi_safi == 'IPV6_UNICAST': - samp.update({'ipv6-unicast': ip_dict}) - if each.get('activate', None) is not None: - enabled = each['activate'] - if enabled is not None: - samp.update({'config': {'enabled': enabled}}) - if each.get('allowas_in', None) is not None: + if pfx_lmt_cfg and afi_safi == 'L2VPN_EVPN': + self._module.fail_json('Prefix limit configuration not supported for l2vpn evpn') + if each.get('ip_afi'): + afi_safi_cfg = get_ip_afi_cfg_payload(each['ip_afi']) + if afi_safi_cfg: + ip_dict.update({'config': afi_safi_cfg}) + if pfx_lmt_cfg: + ip_dict.update({'prefix-limit': {'config': pfx_lmt_cfg}}) + if ip_dict and afi_safi == 'IPV4_UNICAST': + samp.update({'ipv4-unicast': ip_dict}) + if ip_dict and afi_safi == 'IPV6_UNICAST': + samp['ipv6-unicast'] = ip_dict + if each.get('activate') is not None: + samp['config'] = {'enabled': each['activate']} + if each.get('allowas_in'): have_pg_af = self.find_af(have, bgp_as, vrf_name, peer_group, each['afi'], each['safi']) - if each['allowas_in'].get('origin', None) is not None: - if have_pg_af: - if have_pg_af.get('allowas_in', None) is not None: - if have_pg_af['allowas_in'].get('value', None) is not None: - del_nei = {} - del_nei.update({'name': peer_group['name']}) - afis_list = [] - temp_cfg = {'afi': each['afi'], 'safi': each['safi']} - temp_cfg['allowas_in'] = {'value': have_pg_af['allowas_in']['value']} - afis_list.append(temp_cfg) - del_nei.update({'address_family': {'afis': afis_list}}) - requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei)) - origin = each['allowas_in']['origin'] + origin = each['allowas_in'].get('origin') + value = each['allowas_in'].get('value') + if origin is not None: + if have_pg_af is not None: + if have_pg_af.get('allowas_in') is not None and have_pg_af['allowas_in'].get('value') is not None: + del_nei = { + 'name': peer_group['name'], + 'address_family': { + 'afis': [{ + 'afi': each['afi'], + 'safi': each['safi'], + 'allowas_in': {'value': have_pg_af['allowas_in']['value']} + }] + } + } + requests.extend(self.get_delete_specific_peergroup_param_requests(vrf_name, del_nei)) samp.update({'allow-own-as': {'config': {'origin': origin, "enabled": bool("true")}}}) - if each['allowas_in'].get('value', None) is not None: - if have_pg_af: - if have_pg_af.get('allowas_in', None) is not None: - if have_pg_af['allowas_in'].get('origin', None) is not None: - del_nei = {} - del_nei.update({'name': peer_group['name']}) - afis_list = [] - temp_cfg = {'afi': each['afi'], 'safi': each['safi']} - temp_cfg['allowas_in'] = {'origin': have_pg_af['allowas_in']['origin']} - afis_list.append(temp_cfg) - del_nei.update({'address_family': {'afis': afis_list}}) - requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei)) - as_count = each['allowas_in']['value'] - samp.update({'allow-own-as': {'config': {'as-count': as_count, "enabled": bool("true")}}}) - if each.get('prefix_list_in', None) is not None: - prefix_list_in = each['prefix_list_in'] - if prefix_list_in is not None: - pfx_lst_cfg.update({'import-policy': prefix_list_in}) - if each.get('prefix_list_out', None) is not None: - prefix_list_out = each['prefix_list_out'] - if prefix_list_out is not None: - pfx_lst_cfg.update({'export-policy': prefix_list_out}) + if value is not None: + if have_pg_af is not None: + if have_pg_af.get('allowas_in') is not None and have_pg_af['allowas_in'].get('origin') is not None: + del_nei = { + 'name': peer_group['name'], + 'address_family': { + 'afis': [{ + 'afi': each['afi'], + 'safi': each['safi'], + 'allowas_in': {'origin': have_pg_af['allowas_in']['origin']} + }] + } + } + requests.extend(self.get_delete_specific_peergroup_param_requests(vrf_name, del_nei)) + samp['allow-own-as'] = {'config': {'as-count': value, "enabled": bool("true")}} + if each.get('prefix_list_in'): + pfx_lst_cfg['import-policy'] = each['prefix_list_in'] + if each.get('prefix_list_out'): + pfx_lst_cfg['export-policy'] = each['prefix_list_out'] if pfx_lst_cfg: - samp.update({'prefix-list': {'config': pfx_lst_cfg}}) + samp['prefix-list'] = {'config': pfx_lst_cfg} if samp: afi.append(samp) - if tmp_bfd: - bgp_peer_group.update({'enable-bfd': {'config': tmp_bfd}}) - if tmp_ebgp: - bgp_peer_group.update({'ebgp-multihop': {'config': tmp_ebgp}}) - if tmp_timers: - bgp_peer_group.update({'timers': {'config': tmp_timers}}) - if tmp_transport: - bgp_peer_group.update({'transport': {'config': tmp_transport}}) + + self.update_dict(tmp_timers, bgp_peer_group, '', '', {'timers': {'config': tmp_timers}}) + self.update_dict(tmp_bfd, bgp_peer_group, '', '', {'enable-bfd': {'config': tmp_bfd}}) + self.update_dict(tmp_ebgp, bgp_peer_group, '', '', {'ebgp-multihop': {'config': tmp_ebgp}}) + self.update_dict(tmp_capability, peer_group_cfg, '', '', tmp_capability) + self.update_dict(tmp_transport, bgp_peer_group, '', '', {'transport': {'config': tmp_transport}}) + self.update_dict(tmp_remote, peer_group_cfg, '', '', tmp_remote) + self.update_dict(peer_group_cfg, bgp_peer_group, '', '', {'config': peer_group_cfg}) if afi and len(afi) > 0: - bgp_peer_group.update({'afi-safis': {'afi-safi': afi}}) - if tmp_capability: - peer_group_cfg.update(tmp_capability) - if tmp_remote: - peer_group_cfg.update(tmp_remote) - if peer_group_cfg: - bgp_peer_group.update({'config': peer_group_cfg}) + bgp_peer_group['afi-safis'] = {'afi-safi': afi} if bgp_peer_group: bgp_peer_group_list.append(bgp_peer_group) payload = {'openconfig-network-instance:peer-groups': {'peer-group': bgp_peer_group_list}} return payload, requests - def find_pg(self, have, bgp_as, vrf_name, peergroup): - mat_dict = next((m_peer for m_peer in have if m_peer['bgp_as'] == bgp_as and m_peer['vrf_name'] == vrf_name), None) - if mat_dict and mat_dict.get("peer_group", None) is not None: - mat_pg = next((m for m in mat_dict['peer_group'] if m["name"] == peergroup['name']), None) - return mat_pg - - def find_af(self, have, bgp_as, vrf_name, peergroup, afi, safi): - mat_pg = self.find_pg(have, bgp_as, vrf_name, peergroup) - if mat_pg and mat_pg.get('address_family', None) and \ - mat_pg['address_family'].get('afis', None) is not None: - - mat_af = next((af for af in mat_pg['address_family']['afis'] if af['afi'] == afi and af['safi'] == safi), None) - return mat_af - - def find_nei(self, have, bgp_as, vrf_name, neighbor): - mat_dict = next((m_neighbor for m_neighbor in have if m_neighbor['bgp_as'] == bgp_as and m_neighbor['vrf_name'] == vrf_name), None) - if mat_dict and mat_dict.get("neighbors", None) is not None: - mat_neighbor = next((m for m in mat_dict['neighbors'] if m["neighbor"] == neighbor['neighbor']), None) - return mat_neighbor - def build_bgp_neighbors_payload(self, cmd, have, bgp_as, vrf_name): bgp_neighbor_list = [] requests = [] for neighbor in cmd: if neighbor: - bgp_neighbor = {} - neighbor_cfg = {} - tmp_bfd = {} - tmp_ebgp = {} - tmp_timers = {} - tmp_capability = {} - tmp_remote = {} - tmp_transport = {} - if neighbor.get('bfd', None) is not None: - if neighbor['bfd'].get('enabled', None) is not None: - tmp_bfd.update({'enabled': neighbor['bfd']['enabled']}) - if neighbor['bfd'].get('check_failure', None) is not None: - tmp_bfd.update({'check-control-plane-failure': neighbor['bfd']['check_failure']}) - if neighbor['bfd'].get('profile', None) is not None: - tmp_bfd.update({'bfd-profile': neighbor['bfd']['profile']}) - if neighbor.get('auth_pwd', None) is not None: - if (neighbor['auth_pwd'].get('pwd', None) is not None and - neighbor['auth_pwd'].get('encrypted', None) is not None): - bgp_neighbor.update({'auth-password': {'config': {'password': neighbor['auth_pwd']['pwd'], - 'encrypted': neighbor['auth_pwd']['encrypted']}}}) - if neighbor.get('ebgp_multihop', None) is not None: - if neighbor['ebgp_multihop'].get('enabled', None) is not None: - tmp_ebgp.update({'enabled': neighbor['ebgp_multihop']['enabled']}) - if neighbor['ebgp_multihop'].get('multihop_ttl', None) is not None: - tmp_ebgp.update({'multihop-ttl': neighbor['ebgp_multihop']['multihop_ttl']}) - if neighbor.get('timers', None) is not None: - if neighbor['timers'].get('holdtime', None) is not None: - tmp_timers.update({'hold-time': neighbor['timers']['holdtime']}) - if neighbor['timers'].get('keepalive', None) is not None: - tmp_timers.update({'keepalive-interval': neighbor['timers']['keepalive']}) - if neighbor['timers'].get('connect_retry', None) is not None: - tmp_timers.update({'connect-retry': neighbor['timers']['connect_retry']}) - if neighbor.get('capability', None) is not None: - if neighbor['capability'].get('dynamic', None) is not None: - tmp_capability.update({'capability-dynamic': neighbor['capability']['dynamic']}) - if neighbor['capability'].get('extended_nexthop', None) is not None: - tmp_capability.update({'capability-extended-nexthop': neighbor['capability']['extended_nexthop']}) - if neighbor.get('advertisement_interval', None) is not None: - tmp_timers.update({'minimum-advertisement-interval': neighbor['advertisement_interval']}) - if neighbor.get('neighbor', None) is not None: - bgp_neighbor.update({'neighbor-address': neighbor['neighbor']}) - neighbor_cfg.update({'neighbor-address': neighbor['neighbor']}) - if neighbor.get('peer_group', None) is not None: - neighbor_cfg.update({'peer-group': neighbor['peer_group']}) - if neighbor.get('nbr_description', None) is not None: - neighbor_cfg.update({'description': neighbor['nbr_description']}) - if neighbor.get('disable_connected_check', None) is not None: - neighbor_cfg.update({'disable-ebgp-connected-route-check': neighbor['disable_connected_check']}) - if neighbor.get('dont_negotiate_capability', None) is not None: - neighbor_cfg.update({'dont-negotiate-capability': neighbor['dont_negotiate_capability']}) - if neighbor.get('enforce_first_as', None) is not None: - neighbor_cfg.update({'enforce-first-as': neighbor['enforce_first_as']}) - if neighbor.get('enforce_multihop', None) is not None: - neighbor_cfg.update({'enforce-multihop': neighbor['enforce_multihop']}) - if neighbor.get('override_capability', None) is not None: - neighbor_cfg.update({'override-capability': neighbor['override_capability']}) - if neighbor.get('port', None) is not None: - neighbor_cfg.update({'peer-port': neighbor['port']}) - if neighbor.get('shutdown_msg', None) is not None: - neighbor_cfg.update({'shutdown-message': neighbor['shutdown_msg']}) - if neighbor.get('solo', None) is not None: - neighbor_cfg.update({'solo-peer': neighbor['solo']}) - if neighbor.get('strict_capability_match', None) is not None: - neighbor_cfg.update({'strict-capability-match': neighbor['strict_capability_match']}) - if neighbor.get('ttl_security', None) is not None: - neighbor_cfg.update({'ttl-security-hops': neighbor['ttl_security']}) - if neighbor.get('v6only', None) is not None: - neighbor_cfg.update({'openconfig-bgp-ext:v6only': neighbor['v6only']}) - if neighbor.get('local_as', None) is not None: - if neighbor['local_as'].get('as', None) is not None: - neighbor_cfg.update({'local-as': neighbor['local_as']['as']}) - if neighbor['local_as'].get('no_prepend', None) is not None: - neighbor_cfg.update({'local-as-no-prepend': neighbor['local_as']['no_prepend']}) - if neighbor['local_as'].get('replace_as', None) is not None: - neighbor_cfg.update({'local-as-replace-as': neighbor['local_as']['replace_as']}) - if neighbor.get('local_address', None) is not None: - tmp_transport.update({'local-address': neighbor['local_address']}) - if neighbor.get('passive', None) is not None: - tmp_transport.update({'passive-mode': neighbor['passive']}) + bgp_neighbor, neighbor_cfg = {}, {} + tmp_bfd, tmp_ebgp, tmp_capability = {}, {}, {} + tmp_transport, tmp_timers, tmp_remote = {}, {}, {} + + self.update_dict(neighbor, bgp_neighbor, 'neighbor', 'neighbor-address') + self.update_dict(neighbor, neighbor_cfg, 'neighbor', 'neighbor-address') + + if neighbor.get('bfd') is not None: + self.update_dict(neighbor['bfd'], tmp_bfd, 'enabled', 'enabled') + self.update_dict(neighbor['bfd'], tmp_bfd, 'check_failure', 'check-control-plane-failure') + self.update_dict(neighbor['bfd'], tmp_bfd, 'profile', 'bfd-profile') + + if neighbor.get('auth_pwd') is not None: + if (neighbor['auth_pwd'].get('pwd') is not None and neighbor['auth_pwd'].get('encrypted') is not None): + bgp_neighbor['auth-password'] = {'config': {'password': neighbor['auth_pwd']['pwd'], 'encrypted': neighbor['auth_pwd']['encrypted']}} + + if neighbor.get('ebgp_multihop') is not None: + self.update_dict(neighbor['ebgp_multihop'], tmp_ebgp, 'enabled', 'enabled') + self.update_dict(neighbor['ebgp_multihop'], tmp_ebgp, 'multihop_ttl', 'multihop-ttl') + + if neighbor.get('timers') is not None: + self.update_dict(neighbor['timers'], tmp_timers, 'holdtime', 'hold-time') + self.update_dict(neighbor['timers'], tmp_timers, 'keepalive', 'keepalive-interval') + self.update_dict(neighbor['timers'], tmp_timers, 'connect_retry', 'connect-retry') + + if neighbor.get('capability') is not None: + self.update_dict(neighbor['capability'], tmp_capability, 'dynamic', 'capability-dynamic') + self.update_dict(neighbor['capability'], tmp_capability, 'extended_nexthop', 'capability-extended-nexthop') + + self.update_dict(neighbor, neighbor_cfg, 'peer_group', 'peer-group') + self.update_dict(neighbor, neighbor_cfg, 'nbr_description', 'description') + self.update_dict(neighbor, neighbor_cfg, 'disable_connected_check', 'disable-ebgp-connected-route-check') + self.update_dict(neighbor, neighbor_cfg, 'dont_negotiate_capability', 'dont-negotiate-capability') + self.update_dict(neighbor, neighbor_cfg, 'enforce_first_as', 'enforce-first-as') + self.update_dict(neighbor, neighbor_cfg, 'enforce_multihop', 'enforce-multihop') + self.update_dict(neighbor, neighbor_cfg, 'override_capability', 'override-capability') + self.update_dict(neighbor, neighbor_cfg, 'shutdown_msg', 'shutdown-message') + self.update_dict(neighbor, neighbor_cfg, 'solo', 'solo-peer') + self.update_dict(neighbor, neighbor_cfg, 'port', 'peer-port') + self.update_dict(neighbor, neighbor_cfg, 'v6only', 'openconfig-bgp-ext:v6only') + self.update_dict(neighbor, neighbor_cfg, 'strict_capability_match', 'strict-capability-match') + self.update_dict(neighbor, neighbor_cfg, 'ttl_security', 'ttl-security-hops') + + if neighbor.get('local_as') is not None: + self.update_dict(neighbor['local_as'], neighbor_cfg, 'as', 'local-as') + self.update_dict(neighbor['local_as'], neighbor_cfg, 'no_prepend', 'local-as-no-prepend') + self.update_dict(neighbor['local_as'], neighbor_cfg, 'replace_as', 'local-as-replace-as') + + self.update_dict(neighbor, tmp_transport, 'local_address', 'local-address') + self.update_dict(neighbor, tmp_transport, 'passive', 'passive-mode') + self.update_dict(neighbor, tmp_timers, 'advertisement_interval', 'minimum-advertisement-interval') + if neighbor.get('remote_as', None) is not None: have_nei = self.find_nei(have, bgp_as, vrf_name, neighbor) if neighbor['remote_as'].get('peer_as', None) is not None: if have_nei: if have_nei.get("remote_as", None) is not None: if have_nei["remote_as"].get("peer_type", None) is not None: - del_nei = {} - del_nei.update({'neighbor': have_nei['neighbor']}) - del_nei.update({'remote_as': have_nei['remote_as']}) - requests.extend(self.delete_specific_param_request(vrf_name, del_nei)) - tmp_remote.update({'peer-as': neighbor['remote_as']['peer_as']}) + del_nei = { + 'neighbor': have_nei['neighbor'], + 'remote_as': have_nei['remote_as'] + } + requests.extend(self.get_delete_specific_neighbor_param_requests(vrf_name, del_nei)) + tmp_remote['peer-as'] = neighbor['remote_as']['peer_as'].to_request_attr_fmt() if neighbor['remote_as'].get('peer_type', None) is not None: if have_nei: if have_nei.get("remote_as", None) is not None: if have_nei["remote_as"].get("peer_as", None) is not None: - del_nei = {} - del_nei.update({'neighbor': have_nei['neighbor']}) - del_nei.update({'remote_as': have_nei['remote_as']}) - requests.extend(self.delete_specific_param_request(vrf_name, del_nei)) - tmp_remote.update({'peer-type': neighbor['remote_as']['peer_type'].upper()}) - if tmp_bfd: - bgp_neighbor.update({'enable-bfd': {'config': tmp_bfd}}) - if tmp_ebgp: - bgp_neighbor.update({'ebgp-multihop': {'config': tmp_ebgp}}) - if tmp_timers: - bgp_neighbor.update({'timers': {'config': tmp_timers}}) - if tmp_transport: - bgp_neighbor.update({'transport': {'config': tmp_transport}}) - if tmp_capability: - neighbor_cfg.update(tmp_capability) - if tmp_remote: - neighbor_cfg.update(tmp_remote) - if neighbor_cfg: - bgp_neighbor.update({'config': neighbor_cfg}) + del_nei = { + 'neighbor': have_nei['neighbor'], + 'remote_as': have_nei['remote_as'] + } + requests.extend(self.get_delete_specific_neighbor_param_requests(vrf_name, del_nei)) + tmp_remote['peer-type'] = neighbor['remote_as']['peer_type'].upper() + + self.update_dict(tmp_timers, bgp_neighbor, '', '', {'timers': {'config': tmp_timers}}) + self.update_dict(tmp_bfd, bgp_neighbor, '', '', {'enable-bfd': {'config': tmp_bfd}}) + self.update_dict(tmp_ebgp, bgp_neighbor, '', '', {'ebgp-multihop': {'config': tmp_ebgp}}) + self.update_dict(tmp_capability, neighbor_cfg, '', '', tmp_capability) + self.update_dict(tmp_transport, bgp_neighbor, '', '', {'transport': {'config': tmp_transport}}) + self.update_dict(tmp_remote, neighbor_cfg, '', '', tmp_remote) + self.update_dict(neighbor_cfg, bgp_neighbor, '', '', {'config': neighbor_cfg}) + if bgp_neighbor: bgp_neighbor_list.append(bgp_neighbor) payload = {'openconfig-network-instance:neighbors': {'neighbor': bgp_neighbor_list}} @@ -699,186 +781,145 @@ def get_modify_bgp_requests(self, commands, have): return requests for cmd in commands: - edit_path = '%s=%s/%s' % (self.network_instance_path, cmd['vrf_name'], self.protocol_bgp_path) + edit_path = '{0}={1}/{2}'.format(self.network_instance_path, cmd['vrf_name'], self.protocol_bgp_path) if 'peer_group' in cmd and cmd['peer_group']: edit_peer_groups_payload, edit_requests = self.build_bgp_peer_groups_payload(cmd['peer_group'], have, cmd['bgp_as'], cmd['vrf_name']) edit_peer_groups_path = edit_path + '/peer-groups' - if edit_requests: - requests.extend(edit_requests) + requests.extend(edit_requests) requests.append({'path': edit_peer_groups_path, 'method': PATCH, 'data': edit_peer_groups_payload}) if 'neighbors' in cmd and cmd['neighbors']: edit_neighbors_payload, edit_requests = self.build_bgp_neighbors_payload(cmd['neighbors'], have, cmd['bgp_as'], cmd['vrf_name']) edit_neighbors_path = edit_path + '/neighbors' - if edit_requests: - requests.extend(edit_requests) + requests.extend(edit_requests) requests.append({'path': edit_neighbors_path, 'method': PATCH, 'data': edit_neighbors_payload}) return requests - def get_delete_specific_bgp_peergroup_param_request(self, vrf_name, cmd, want_match): - requests = [] - want_peer_group = want_match.get('peer_group', None) - for each in cmd['peer_group']: - if each: - name = each.get('name', None) - remote_as = each.get('remote_as', None) - timers = each.get('timers', None) - advertisement_interval = each.get('advertisement_interval', None) - bfd = each.get('bfd', None) - capability = each.get('capability', None) - auth_pwd = each.get('auth_pwd', None) - pg_description = each.get('pg_description', None) - disable_connected_check = each.get('disable_connected_check', None) - dont_negotiate_capability = each.get('dont_negotiate_capability', None) - ebgp_multihop = each.get('ebgp_multihop', None) - enforce_first_as = each.get('enforce_first_as', None) - enforce_multihop = each.get('enforce_multihop', None) - local_address = each.get('local_address', None) - local_as = each.get('local_as', None) - override_capability = each.get('override_capability', None) - passive = each.get('passive', None) - shutdown_msg = each.get('shutdown_msg', None) - solo = each.get('solo', None) - strict_capability_match = each.get('strict_capability_match', None) - ttl_security = each.get('ttl_security', None) - address_family = each.get('address_family', None) - if (name and not remote_as and not timers and not advertisement_interval and not bfd and not capability and not auth_pwd and not - pg_description and disable_connected_check is None and dont_negotiate_capability is None and not ebgp_multihop and - enforce_first_as is None and enforce_multihop is None and not local_address and not local_as and override_capability - is None and passive is None and not shutdown_msg and solo is None and strict_capability_match is None and not ttl_security and - not address_family): - want_pg_match = None - if want_peer_group: - want_pg_match = next((cfg for cfg in want_peer_group if cfg['name'] == name), None) - if want_pg_match: - keys = ['remote_as', 'timers', 'advertisement_interval', 'bfd', 'capability', 'auth_pwd', 'pg_description', - 'disable_connected_check', 'dont_negotiate_capability', 'ebgp_multihop', 'enforce_first_as', 'enforce_multihop', - 'local_address', 'local_as', 'override_capability', 'passive', 'shutdown_msg', 'solo', 'strict_capability_match', - 'ttl_security', 'address_family'] - if not any(want_pg_match.get(key, None) for key in keys): - requests.append(self.get_delete_vrf_specific_peergroup_request(vrf_name, name)) + def get_delete_bgp_peergroup_commands_requests(self, vrf_name, want_peergroup, have_peergroup): + commands, requests = [], [] + for pg in want_peergroup: + have_pg = next((cfg for cfg in have_peergroup if cfg['name'] == pg['name']), None) + if have_pg: + pg = remove_empties(pg) + if len(pg) == 1 and pg.get('name'): + commands.append({'name': pg['name']}) + requests.append(self.delete_peergroup_whole_request(vrf_name, pg['name'])) else: - requests.extend(self.delete_specific_peergroup_param_request(vrf_name, each)) - return requests + cmd = {} + for attr in pg: + if attr != 'name': + return_object = self._get_common_in_dict(pg[attr], have_pg.get(attr)) + if return_object is not None: + cmd[attr] = return_object + if cmd: + cmd['name'] = pg['name'] + commands.append(cmd) + requests.extend(self.get_delete_specific_peergroup_param_requests(vrf_name, cmd)) + return commands, requests - def delete_specific_peergroup_param_request(self, vrf_name, cmd): + def get_delete_bgp_neighbor_commands_requests(self, vrf_name, want_neighbors, have_neighbors): + commands, requests = [], [] + for nbr in want_neighbors: + have_nbr = next((cfg for cfg in have_neighbors if cfg['neighbor'] == nbr['neighbor']), None) + if have_nbr: + nbr = remove_empties(nbr) + if len(nbr) == 1 and nbr.get('neighbor'): + commands.append({'neighbor': nbr['neighbor']}) + requests.append(self.delete_neighbor_whole_request(vrf_name, nbr['neighbor'])) + else: + cmd = {} + for attr in nbr: + if attr != 'neighbor': + return_object = self._get_common_in_dict(nbr[attr], have_nbr.get(attr)) + if return_object is not None: + cmd[attr] = return_object + if cmd: + cmd['neighbor'] = nbr['neighbor'] + commands.append(cmd) + requests.extend(self.get_delete_specific_neighbor_param_requests(vrf_name, cmd)) + return commands, requests + + def get_delete_specific_peergroup_param_requests(self, vrf_name, cmd): requests = [] - delete_static_path = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) - delete_static_path = delete_static_path + '/peer-groups/peer-group=%s' % (cmd['name']) - if cmd.get('remote_as', None) is not None: - if cmd['remote_as'].get('peer_as', None) is not None: - delete_path = delete_static_path + '/config/peer-as' - requests.append({'path': delete_path, 'method': DELETE}) - elif cmd['remote_as'].get('peer_type', None) is not None: - delete_path = delete_static_path + '/config/peer-type' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('advertisement_interval', None) is not None: - delete_path = delete_static_path + '/timers/config/minimum-advertisement-interval' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('timers', None) is not None: - if cmd['timers'].get('holdtime', None) is not None: - delete_path = delete_static_path + '/timers/config/hold-time' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['timers'].get('keepalive', None) is not None: - delete_path = delete_static_path + '/timers/config/keepalive-interval' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['timers'].get('connect_retry', None) is not None: - delete_path = delete_static_path + '/timers/config/connect-retry' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('capability', None) is not None: - if cmd['capability'].get('dynamic', None) is not None: - delete_path = delete_static_path + '/config/capability-dynamic' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['capability'].get('extended_nexthop', None) is not None: - delete_path = delete_static_path + '/config/capability-extended-nexthop' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('pg_description', None) is not None: - delete_path = delete_static_path + '/config/description' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('disable_connected_check', None) is not None: - delete_path = delete_static_path + '/config/disable-ebgp-connected-route-check' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('dont_negotiate_capability', None) is not None: - delete_path = delete_static_path + '/config/dont-negotiate-capability' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('enforce_first_as', None) is not None: - delete_path = delete_static_path + '/config/enforce-first-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('enforce_multihop', None) is not None: - delete_path = delete_static_path + '/config/enforce-multihop' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('override_capability', None) is not None: - delete_path = delete_static_path + '/config/override-capability' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('shutdown_msg', None) is not None: - delete_path = delete_static_path + '/config/shutdown-message' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('solo', None) is not None: - delete_path = delete_static_path + '/config/solo-peer' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('strict_capability_match', None) is not None: - delete_path = delete_static_path + '/config/strict-capability-match' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('ttl_security', None) is not None: - delete_path = delete_static_path + '/config/ttl-security-hops' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('local_as', None) is not None: - if cmd['local_as'].get('as', None) is not None: - delete_path = delete_static_path + '/config/local-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['local_as'].get('no_prepend', None) is not None: - delete_path = delete_static_path + '/config/local-as-no-prepend' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['local_as'].get('replace_as', None) is not None: - delete_path = delete_static_path + '/config/local-as-replace-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('local_address', None) is not None: - delete_path = delete_static_path + '/transport/config/local-address' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('passive', None) is not None: - delete_path = delete_static_path + '/transport/config/passive-mode' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('bfd', None) is not None: - if cmd['bfd'].get('enabled', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/enabled' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['bfd'].get('check_failure', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/check-control-plane-failure' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['bfd'].get('profile', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/bfd-profile' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('auth_pwd', None) is not None: - if cmd['auth_pwd'].get('pwd', None) is not None: - delete_path = delete_static_path + '/auth-password/config/password' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['auth_pwd'].get('encrypted', None) is not None: - delete_path = delete_static_path + '/auth-password/config/encrypted' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('ebgp_multihop', None) is not None: - if cmd['ebgp_multihop'].get('enabled', None) is not None: - delete_path = delete_static_path + '/ebgp-multihop/config/enabled' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['ebgp_multihop'].get('multihop_ttl', None) is not None: - delete_path = delete_static_path + '/ebgp-multihop/config/multihop-ttl' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('address_family', None) is not None: - if cmd['address_family'].get('afis', None) is None: + delete_static_path = '{0}={1}/{2}/peer-groups/peer-group={3}'.format(self.network_instance_path, vrf_name, self.protocol_bgp_path, cmd['name']) + peergroup_request_path = { + 'remote_as': { + 'peer_as': '/config/peer-as', + 'peer_type': '/config/peer-type' + }, + 'advertisement_interval': '/timers/config/minimum-advertisement-interval', + 'timers': { + 'holdtime': '/timers/config/hold-time', + 'keepalive': '/timers/config/keepalive-interval', + 'connect_retry': '/timers/config/connect-retry' + }, + 'capability': { + 'dynamic': '/config/capability-dynamic', + 'extended_nexthop': '/config/capability-extended-nexthop' + }, + 'pg_description': '/config/description', + 'disable_connected_check': '/config/disable-ebgp-connected-route-check', + 'dont_negotiate_capability': '/config/dont-negotiate-capability', + 'enforce_first_as': '/config/enforce-first-as', + 'enforce_multihop': '/config/enforce-multihop', + 'override_capability': '/config/override-capability', + 'shutdown_msg': '/config/shutdown-message', + 'solo': '/config/solo-peer', + 'strict_capability_match': '/config/strict-capability-match', + 'ttl_security': '/config/ttl-security-hops', + 'local_as': { + 'as': '/config/local-as', + 'no_prepend': '/config/local-as-no-prepend', + 'replace_as': '/config/local-as-replace-as' + }, + 'local_address': '/transport/config/local-address', + 'passive': '/transport/config/passive-mode', + 'bfd': { + 'enabled': '/enable-bfd/config/enabled', + 'check_failure': '/enable-bfd/config/check-control-plane-failure', + 'profile': '/enable-bfd/config/bfd-profile' + }, + 'auth_pwd': { + 'pwd': '/auth-password/config/password', + 'encrypted': '/auth-password/config/encrypted' + }, + 'ebgp_multihop': { + 'enabled': '/ebgp-multihop/config/enabled', + 'multihop_ttl': '/ebgp-multihop/config/multihop-ttl' + } + } + + for attr, value in peergroup_request_path.items(): + if cmd.get(attr) is not None: + if attr == 'local_as': + for local_as_attr in value: + delete_path = delete_static_path + value[local_as_attr] + requests.append({'path': delete_path, 'method': DELETE}) + elif isinstance(value, dict): + for dict_attr in value: + if cmd[attr].get(dict_attr) is not None: + delete_path = delete_static_path + value[dict_attr] + requests.append({'path': delete_path, 'method': DELETE}) + else: + delete_path = delete_static_path + value + requests.append({'path': delete_path, 'method': DELETE}) + + if cmd.get('address_family') is not None: + if cmd['address_family'].get('afis') is None: delete_path = delete_static_path + '/afi-safis/afi-safi' requests.append({'path': delete_path, 'method': DELETE}) else: for each in cmd['address_family']['afis']: - afi = each.get('afi', None) - safi = each.get('safi', None) - activate = each.get('activate', None) - allowas_in = each.get('allowas_in', None) - ip_afi = each.get('ip_afi', None) - prefix_limit = each.get('prefix_limit', None) - prefix_list_in = each.get('prefix_list_in', None) - prefix_list_out = each.get('prefix_list_out', None) + afi = each.get('afi') + safi = each.get('safi') + activate = each.get('activate') + allowas_in = each.get('allowas_in') + ip_afi = each.get('ip_afi') + prefix_limit = each.get('prefix_limit') + prefix_list_in = each.get('prefix_list_in') + prefix_list_out = each.get('prefix_list_out') afi_safi = afi.upper() + '_' + safi.upper() afi_safi_name = 'openconfig-bgp-types:' + afi_safi - if (afi and safi and not activate and not allowas_in and not ip_afi and not prefix_limit and not prefix_list_in - and not prefix_list_out): + if afi and safi and not any([activate, allowas_in, ip_afi, prefix_limit, prefix_list_in, prefix_list_out]): delete_path = delete_static_path + '/afi-safis/afi-safi=%s' % (afi_safi_name) requests.append({'path': delete_path, 'method': DELETE}) else: @@ -886,10 +927,10 @@ def delete_specific_peergroup_param_request(self, vrf_name, cmd): delete_path = delete_static_path + '/afi-safis/afi-safi=%s/config/enabled' % (afi_safi_name) requests.append({'path': delete_path, 'method': DELETE}) if allowas_in: - if allowas_in.get('origin', None): + if allowas_in.get('origin'): delete_path = delete_static_path + '/afi-safis/afi-safi=%s/allow-own-as/config/origin' % (afi_safi_name) requests.append({'path': delete_path, 'method': DELETE}) - if allowas_in.get('value', None): + if allowas_in.get('value'): delete_path = delete_static_path + '/afi-safis/afi-safi=%s/allow-own-as/config/as-count' % (afi_safi_name) requests.append({'path': delete_path, 'method': DELETE}) if prefix_list_in: @@ -911,25 +952,96 @@ def delete_specific_peergroup_param_request(self, vrf_name, cmd): return requests + def get_delete_specific_neighbor_param_requests(self, vrf_name, cmd): + requests = [] + neighbor = cmd['neighbor'].replace('/', '%2f') + delete_static_path = '{0}={1}/{2}/neighbors/neighbor={3}'.format(self.network_instance_path, vrf_name, self.protocol_bgp_path, neighbor) + nbr_request_path = { + 'remote_as': { + 'peer_as': '/config/peer-as', + 'peer_type': '/config/peer-type' + }, + 'peer_group': '/config/peer-group', + 'advertisement_interval': '/timers/config/minimum-advertisement-interval', + 'timers': { + 'holdtime': '/timers/config/hold-time', + 'keepalive': '/timers/config/keepalive-interval', + 'connect_retry': '/timers/config/connect-retry' + }, + 'capability': { + 'dynamic': '/config/capability-dynamic', + 'extended_nexthop': '/config/capability-extended-nexthop' + }, + 'nbr_description': '/config/description', + 'disable_connected_check': '/config/disable-ebgp-connected-route-check', + 'dont_negotiate_capability': '/config/dont-negotiate-capability', + 'enforce_first_as': '/config/enforce-first-as', + 'enforce_multihop': '/config/enforce-multihop', + 'override_capability': '/config/override-capability', + 'shutdown_msg': '/config/shutdown-message', + 'solo': '/config/solo-peer', + 'port': '/config/peer-port', + 'strict_capability_match': '/config/strict-capability-match', + 'ttl_security': '/config/ttl-security-hops', + 'v6only': '/config/openconfig-bgp-ext:v6only', + 'local_as': { + 'as': '/config/local-as', + 'no_prepend': '/config/local-as-no-prepend', + 'replace_as': '/config/local-as-replace-as' + }, + 'local_address': '/transport/config/local-address', + 'passive': '/transport/config/passive-mode', + 'bfd': { + 'enabled': '/enable-bfd/config/enabled', + 'check_failure': '/enable-bfd/config/check-control-plane-failure', + 'profile': '/enable-bfd/config/bfd-profile' + }, + 'auth_pwd': { + 'pwd': '/auth-password/config/password', + 'encrypted': '/auth-password/config/encrypted' + }, + 'ebgp_multihop': { + 'enabled': '/ebgp-multihop/config/enabled', + 'multihop_ttl': '/ebgp-multihop/config/multihop-ttl' + } + } + + for attr, value in nbr_request_path.items(): + if cmd.get(attr) is not None: + if attr == 'local_as': + for local_as_attr in value: + delete_path = delete_static_path + value[local_as_attr] + requests.append({'path': delete_path, 'method': DELETE}) + elif isinstance(value, dict): + for dict_attr in value: + if cmd[attr].get(dict_attr) is not None: + delete_path = delete_static_path + value[dict_attr] + requests.append({'path': delete_path, 'method': DELETE}) + else: + delete_path = delete_static_path + value + requests.append({'path': delete_path, 'method': DELETE}) + + return requests + def delete_ip_afi_requests(self, ip_afi, afi_safi_name, afi_safi, delete_static_path): requests = [] - default_policy_name = ip_afi.get('default_policy_name', None) - send_default_route = ip_afi.get('send_default_route', None) + default_policy_name = ip_afi.get('default_policy_name') + send_default_route = ip_afi.get('send_default_route') if default_policy_name: delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/config/default-policy-name' % (afi_safi_name, afi_safi) requests.append({'path': delete_path, 'method': DELETE}) if send_default_route: - delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/config/send_default_route' % (afi_safi_name, afi_safi) + delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/config/send-default-route' % (afi_safi_name, afi_safi) requests.append({'path': delete_path, 'method': DELETE}) return requests def delete_prefix_limit_requests(self, prefix_limit, afi_safi_name, afi_safi, delete_static_path): requests = [] - max_prefixes = prefix_limit.get('max_prefixes', None) - prevent_teardown = prefix_limit.get('prevent_teardown', None) - warning_threshold = prefix_limit.get('warning_threshold', None) - restart_timer = prefix_limit.get('restart_timer', None) + max_prefixes = prefix_limit.get('max_prefixes') + prevent_teardown = prefix_limit.get('prevent_teardown') + warning_threshold = prefix_limit.get('warning_threshold') + restart_timer = prefix_limit.get('restart_timer') if max_prefixes: delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/prefix-limit/config/max-prefixes' % (afi_safi_name, afi_safi) requests.append({'path': delete_path, 'method': DELETE}) @@ -945,231 +1057,100 @@ def delete_prefix_limit_requests(self, prefix_limit, afi_safi_name, afi_safi, de return requests - def get_delete_specific_bgp_param_request(self, vrf_name, cmd, want_match): - requests = [] - want_neighbors = want_match.get('neighbors', None) - for each in cmd['neighbors']: - if each: - neighbor = each.get('neighbor', None) - remote_as = each.get('remote_as', None) - peer_group = each.get('peer_group', None) - timers = each.get('timers', None) - advertisement_interval = each.get('advertisement_interval', None) - bfd = each.get('bfd', None) - capability = each.get('capability', None) - auth_pwd = each.get('auth_pwd', None) - nbr_description = each.get('nbr_description', None) - disable_connected_check = each.get('disable_connected_check', None) - dont_negotiate_capability = each.get('dont_negotiate_capability', None) - ebgp_multihop = each.get('ebgp_multihop', None) - enforce_first_as = each.get('enforce_first_as', None) - enforce_multihop = each.get('enforce_multihop', None) - local_address = each.get('local_address', None) - local_as = each.get('local_as', None) - override_capability = each.get('override_capability', None) - passive = each.get('passive', None) - port = each.get('port', None) - shutdown_msg = each.get('shutdown_msg', None) - solo = each.get('solo', None) - strict_capability_match = each.get('strict_capability_match', None) - ttl_security = each.get('ttl_security', None) - v6only = each.get('v6only', None) - if (neighbor and not remote_as and not peer_group and not timers and not advertisement_interval and not bfd and not capability and not - auth_pwd and not nbr_description and disable_connected_check is None and dont_negotiate_capability is None and not - ebgp_multihop and enforce_first_as is None and enforce_multihop is None and not local_address and not local_as and - override_capability is None and passive is None and not port and not shutdown_msg and solo is None and strict_capability_match - is None and not ttl_security and v6only is None): - want_nei_match = None - if want_neighbors: - want_nei_match = next(cfg for cfg in want_neighbors if cfg['neighbor'] == neighbor) - if want_nei_match: - keys = ['remote_as', 'peer_group', 'timers', 'advertisement_interval', 'bfd', 'capability', 'auth_pwd', 'nbr_description', - 'disable_connected_check', 'dont_negotiate_capability', 'ebgp_multihop', 'enforce_first_as', 'enforce_multihop', - 'local_address', 'local_as', 'override_capability', 'passive', 'port', 'shutdown_msg', 'solo', - 'strict_capability_match', 'ttl_security', 'v6only'] - if not any(want_nei_match.get(key, None) for key in keys): - requests.append(self.delete_neighbor_whole_request(vrf_name, neighbor)) - else: - requests.extend(self.delete_specific_param_request(vrf_name, each)) - return requests - def delete_neighbor_whole_request(self, vrf_name, neighbor): - requests = [] - url = '%s=%s/%s/%s=%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, neighbor) + url = '{0}={1}/{2}/{3}={4}/'.format(self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, neighbor.replace('/', '%2f')) return ({'path': url, 'method': DELETE}) - def delete_specific_param_request(self, vrf_name, cmd): - requests = [] - delete_static_path = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path) - delete_static_path = delete_static_path + '/neighbors/neighbor=%s' % (cmd['neighbor']) - if cmd.get('remote_as', None) is not None: - if cmd['remote_as'].get('peer_as', None) is not None: - delete_path = delete_static_path + '/config/peer-as' - requests.append({'path': delete_path, 'method': DELETE}) - elif cmd['remote_as'].get('peer_type', None) is not None: - delete_path = delete_static_path + '/config/peer-type' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('peer_group', None) is not None: - delete_path = delete_static_path + '/config/peer-group' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('nbr_description', None) is not None: - delete_path = delete_static_path + '/config/description' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('disable_connected_check', None) is not None: - delete_path = delete_static_path + '/config/disable-ebgp-connected-route-check' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('dont_negotiate_capability', None) is not None: - delete_path = delete_static_path + '/config/dont-negotiate-capability' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('enforce_first_as', None) is not None: - delete_path = delete_static_path + '/config/enforce-first-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('enforce_multihop', None) is not None: - delete_path = delete_static_path + '/config/enforce-multihop' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('override_capability', None) is not None: - delete_path = delete_static_path + '/config/override-capability' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('port', None) is not None: - delete_path = delete_static_path + '/config/peer-port' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('shutdown_msg', None) is not None: - delete_path = delete_static_path + '/config/shutdown-message' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('solo', None) is not None: - delete_path = delete_static_path + '/config/solo-peer' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('strict_capability_match', None) is not None: - delete_path = delete_static_path + '/config/strict-capability-match' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('ttl_security', None) is not None: - delete_path = delete_static_path + '/config/ttl-security-hops' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('v6only', None) is not None: - delete_path = delete_static_path + '/config/openconfig-bgp-ext:v6only' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('local_as', None) is not None: - if cmd['local_as'].get('as', None) is not None: - delete_path = delete_static_path + '/config/local-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['local_as'].get('no_prepend', None) is not None: - delete_path = delete_static_path + '/config/local-as-no-prepend' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['local_as'].get('replace_as', None) is not None: - delete_path = delete_static_path + '/config/local-as-replace-as' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('local_address', None) is not None: - delete_path = delete_static_path + '/transport/config/local-address' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('passive', None) is not None: - delete_path = delete_static_path + '/transport/config/passive-mode' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('advertisement_interval', None) is not None: - delete_path = delete_static_path + '/timers/config/minimum-advertisement-interval' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('timers', None) is not None: - if cmd['timers'].get('holdtime', None) is not None: - delete_path = delete_static_path + '/timers/config/hold-time' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['timers'].get('keepalive', None) is not None: - delete_path = delete_static_path + '/timers/config/keepalive-interval' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['timers'].get('connect_retry', None) is not None: - delete_path = delete_static_path + '/timers/config/connect-retry' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('capability', None) is not None: - if cmd['capability'].get('dynamic', None) is not None: - delete_path = delete_static_path + '/config/capability-dynamic' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['capability'].get('extended_nexthop', None) is not None: - delete_path = delete_static_path + '/config/capability-extended-nexthop' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('bfd', None) is not None: - if cmd['bfd'].get('enabled', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/enabled' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['bfd'].get('check_failure', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/check-control-plane-failure' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['bfd'].get('profile', None) is not None: - delete_path = delete_static_path + '/enable-bfd/config/bfd-profile' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('auth_pwd', None) is not None: - if cmd['auth_pwd'].get('pwd', None) is not None: - delete_path = delete_static_path + '/auth-password/config/password' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['auth_pwd'].get('encrypted', None) is not None: - delete_path = delete_static_path + '/auth-password/config/encrypted' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd.get('ebgp_multihop', None) is not None: - if cmd['ebgp_multihop'].get('enabled', None) is not None: - delete_path = delete_static_path + '/ebgp-multihop/config/enabled' - requests.append({'path': delete_path, 'method': DELETE}) - if cmd['ebgp_multihop'].get('multihop_ttl', None) is not None: - delete_path = delete_static_path + '/ebgp-multihop/config/multihop-ttl' - requests.append({'path': delete_path, 'method': DELETE}) - - return requests - - def get_delete_vrf_specific_neighbor_request(self, vrf_name, have): + def get_delete_vrf_specific_neighbor_request(self, vrf_name, neighbors): requests = [] - for each in have: - if each.get('neighbor', None): + for each in neighbors: + if each.get('neighbor'): requests.append(self.delete_neighbor_whole_request(vrf_name, each['neighbor'])) return requests - def get_delete_vrf_specific_peergroup_request(self, vrf_name, peergroup_name): - requests = [] - delete_neighbor_path = '%s=%s/%s/peer-groups/peer-group=%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, peergroup_name) + def delete_peergroup_whole_request(self, vrf_name, peergroup_name): + delete_neighbor_path = '{0}={1}/{2}/peer-groups/peer-group={3}'.format(self.network_instance_path, vrf_name, self.protocol_bgp_path, peergroup_name) return ({'path': delete_neighbor_path, 'method': DELETE}) - def get_delete_all_bgp_neighbor_requests(self, commands): + def get_delete_all_bgp_neighbor_peergroup_requests(self, commands): requests = [] for cmd in commands: - if cmd.get('neighbors', None): + if cmd.get('neighbors'): requests.extend(self.get_delete_vrf_specific_neighbor_request(cmd['vrf_name'], cmd['neighbors'])) if 'peer_group' in cmd and cmd['peer_group']: for each in cmd['peer_group']: - requests.append(self.get_delete_vrf_specific_peergroup_request(cmd['vrf_name'], each['name'])) + requests.append(self.delete_peergroup_whole_request(cmd['vrf_name'], each['name'])) return requests - def get_delete_bgp_neighbor_requests(self, commands, have, want, is_delete_all): - requests = [] - if is_delete_all: - requests = self.get_delete_all_bgp_neighbor_requests(commands) - else: - for cmd in commands: - vrf_name = cmd['vrf_name'] - as_val = cmd['bgp_as'] - neighbors = cmd.get('neighbors', None) - peer_group = cmd.get('peer_group', None) - want_match = next((cfg for cfg in want if vrf_name == cfg['vrf_name'] and as_val == cfg['bgp_as']), None) - want_neighbors = want_match.get('neighbors', None) - want_peer_group = want_match.get('peer_group', None) - if neighbors is None and peer_group is None and want_neighbors is None and want_peer_group is None: - new_cmd = {} - for each in have: - if vrf_name == each['vrf_name'] and as_val == each['bgp_as']: - new_neighbors = [] - new_pg = [] - if each.get('neighbors', None): - new_neighbors = [{'neighbor': i['neighbor']} for i in each.get('neighbors', None)] - if each.get('peer_group', None): - new_pg = [{'name': i['name']} for i in each.get('peer_group', None)] - if new_neighbors: - new_cmd['neighbors'] = new_neighbors - requests.extend(self.get_delete_vrf_specific_neighbor_request(vrf_name, new_cmd['neighbors'])) - if new_pg: - new_cmd['name'] = new_pg - for each in new_cmd['name']: - requests.append(self.get_delete_vrf_specific_peergroup_request(vrf_name, each['name'])) - break - else: - if neighbors: - requests.extend(self.get_delete_specific_bgp_param_request(vrf_name, cmd, want_match)) - if peer_group: - requests.extend(self.get_delete_specific_bgp_peergroup_param_request(vrf_name, cmd, want_match)) - return requests + def find_pg(self, have, bgp_as, vrf_name, peergroup): + mat_dict = next((m_peer for m_peer in have if m_peer['bgp_as'] == bgp_as and m_peer['vrf_name'] == vrf_name), None) + if mat_dict and mat_dict.get("peer_group", None) is not None: + mat_pg = next((m for m in mat_dict['peer_group'] if m["name"] == peergroup['name']), None) + return mat_pg + + def find_af(self, have, bgp_as, vrf_name, peergroup, afi, safi): + mat_pg = self.find_pg(have, bgp_as, vrf_name, peergroup) + if mat_pg and mat_pg['address_family'].get('afis', None) is not None and mat_pg['address_family'].get('afis', None) is not None: + mat_af = next((af for af in mat_pg['address_family']['afis'] if af['afi'] == afi and af['safi'] == safi), None) + return mat_af + + def find_nei(self, have, bgp_as, vrf_name, neighbor): + mat_dict = next((m_neighbor for m_neighbor in have if m_neighbor['bgp_as'] == bgp_as and m_neighbor['vrf_name'] == vrf_name), None) + if mat_dict and mat_dict.get("neighbors", None) is not None: + mat_neighbor = next((m for m in mat_dict['neighbors'] if m["neighbor"] == neighbor['neighbor']), None) + return mat_neighbor + + def _get_common_in_dict(self, obj, have_obj): + if have_obj is not None: + if not isinstance(obj, dict) and not isinstance(obj, list): + if have_obj == obj: + return obj + elif isinstance(obj, list): + traverse_list = [] + for item in obj: + afi = item.get('afi') + safi = item.get('safi') + if afi and safi: + have_item = next((cfg for cfg in have_obj if cfg['afi'] == afi and cfg['safi'] == safi), None) + if have_item is not None: + if self._has_more_than_afi(have_item): + return_object = self._get_common_in_dict(item, have_item) + if return_object is not None: + traverse_list.append(return_object) + elif not self._has_more_than_afi(item): + return_object = self._get_common_in_dict(have_item, item) + if return_object is not None: + traverse_list.append(return_object) + return (traverse_list or None) + else: + traverse_dict = {} + for key in obj: + return_object = self._get_common_in_dict(obj[key], have_obj.get(key)) + if return_object is not None: + traverse_dict[key] = return_object + return (traverse_dict or None) + return None + + def pre_process_generated_config(self, commands, have): + for conf in commands: + bgp_as = conf.get('bgp_as') + vrf_name = conf.get('vrf_name') + match = next((m for m in have if m['bgp_as'] == bgp_as and m['vrf_name'] == vrf_name), None) + if match: + for attr in ('neighbors', 'peer_group'): + conf_attr = conf.get(attr, []) + match_attr = match.get(attr, []) + if conf_attr and match_attr: + for item in conf_attr: + key = 'neighbor' if attr == 'neighbors' else 'name' + match_item = next((nei for nei in match_attr if nei[key] == item[key]), None) + if match_item: + if 'remote_as' in item and 'remote_as' in match_item: + if 'peer_type' in item.get('remote_as', {}) and 'peer_as' in match_item.get('remote_as', {}): + match_item['remote_as'].pop('peer_as', None) + if 'peer_as' in item.get('remote_as', {}) and 'peer_type' in match_item.get('remote_as', {}): + match_item['remote_as'].pop('peer_type', None) + return have def post_process_generated_config(self, configs): TEST_KEYS_remove_void_config = [ @@ -1178,6 +1159,103 @@ def post_process_generated_config(self, configs): {'afis': {'__test_keys': ('afi', 'safi')}}, ] confs = remove_void_config(configs, TEST_KEYS_remove_void_config) - for default_entry in default_entries: - add_config_defaults(confs, default_entry) + # Add default entries + for conf in confs: + for peer_group in conf.get('peer_group', []): + peer_group.setdefault('ebgp_multihop', {'enabled': False}) + if 'multihop_ttl' in peer_group['ebgp_multihop'] and 'enabled' not in peer_group['ebgp_multihop']: + peer_group['ebgp_multihop']['enabled'] = True + + peer_group.setdefault('timers', {'connect_retry': 30}) + peer_group['timers'].setdefault('connect_retry', 30) + peer_group.setdefault('passive', False) + peer_group.setdefault('advertisement_interval', 0) + if 'local_as' in peer_group: + if 'as' in peer_group['local_as'] and peer_group['local_as']['as']: + peer_group['local_as'].setdefault('no_prepend', False) + peer_group['local_as'].setdefault('replace_as', False) + elif 'no_prepend' in peer_group['local_as'] or 'replace_as' in peer_group['local_as']: + peer_group.pop('local_as', None) + if 'address_family' in peer_group: + address_family = peer_group.get('address_family', {}) + if address_family: + for afis in address_family.get('afis', []): + afis.setdefault('activate', False) + if len(afis.get('ip_afi', {})) > 1: + afis['ip_afi'].setdefault('send_default_route', False) + elif 'send_default_route' in afis.get('ip_afi', {}): + afis.pop('ip_afi', None) + if len(afis.get('prefix_limit', {})) > 1: + afis['prefix_limit'].setdefault('prevent_teardown', False) + elif 'prevent_teardown' in afis.get('prefix_limit', {}): + afis.pop('prefix_limit', None) + for neighbor in conf.get('neighbors', []): + neighbor.setdefault('passive', False) + if 'ebgp_multihop' in neighbor and 'multihop_ttl' in neighbor['ebgp_multihop'] and 'enabled' not in neighbor['ebgp_multihop']: + neighbor['ebgp_multihop']['enabled'] = True + if 'local_as' in neighbor: + if 'as' in neighbor['local_as'] and neighbor['local_as']['as']: + neighbor['local_as'].setdefault('no_prepend', False) + neighbor['local_as'].setdefault('replace_as', False) + elif 'no_prepend' in neighbor['local_as'] or 'replace_as' in neighbor['local_as']: + neighbor.pop('local_as', None) + if 'passive' in neighbor and 'neighbor' in neighbor and len(neighbor) > 2: + if 'peer_group' in neighbor or 'remote_as' in neighbor: + if 'advertisement_interval' not in neighbor and 'timers' not in neighbor: + neighbor['advertisement_interval'] = 0 + neighbor['timers'] = {'connect_retry': 30, 'keepalive': 60} return confs + + def _get_skeleton_keys(self, want): + skeleton = {} + for cmd in want: + bgp_as = cmd['bgp_as'].__str__() if cmd.get('bgp_as') else None + vrf_name = cmd.get('vrf_name') + neighbors = [] + peer_group = {} + for neighbor in cmd.get('neighbors', []): + neighbors.append(neighbor.get('neighbor')) + for pg in cmd.get('peer_group', []): + afi = [] + if 'address_family' in pg and pg.get('address_family'): + if 'afis' in pg['address_family'] and pg['address_family'].get('afis'): + for afi_conf in pg['address_family']['afis']: + afi.append(afi_conf.get('afi')) + peer_group[pg.get('name')] = afi + if neighbors or peer_group: + skeleton.setdefault(bgp_as, {}) + skeleton[bgp_as][vrf_name] = { + 'neighbors': neighbors, + 'peer_group': peer_group + } + return skeleton + + def _get_delete_pg_commands(self, have, want_skeleton): + cmd = {} + for attr in have: + if attr == 'address_family' and have.get(attr): + if 'afis' in have.get(attr): + af_cmd = [] + for af in have[attr].get('afis', []): + if af.get('afi') in want_skeleton: + af_cmd.append(af) + else: + af_cmd.append({'afi': af.get('afi'), 'safi': af.get('safi')}) + if af_cmd: + cmd[attr] = {'afis': af_cmd} + else: + cmd[attr] = have[attr] + return cmd + + def update_dict(self, src, dest, src_key, dest_key, value=False): + if not value: + if src.get(src_key) is not None: + if src_key == 'as': + dest[dest_key] = src[src_key].to_request_attr_fmt() + else: + dest[dest_key] = src[src_key] + elif src: + dest.update(value) + + def _has_more_than_afi(self, obj): + return len(obj) > 2 diff --git a/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py b/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py index 93e05bd0d..5b3c2bc2c 100644 --- a/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py +++ b/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py @@ -42,6 +42,7 @@ remove_void_config ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + convert_bgp_asn, validate_bgps, normalize_neighbors_interface_name, get_ip_afi_cfg_payload, @@ -219,6 +220,7 @@ def set_config(self, existing_bgp_neighbors_af_facts): to the desired configuration """ want = self._module.params['config'] + convert_bgp_asn(want) normalize_neighbors_interface_name(want, self._module) have = existing_bgp_neighbors_af_facts resp = self.set_state(want, have) diff --git a/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py index 9543b1920..25c6a56c9 100644 --- a/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py @@ -114,21 +114,21 @@ def execute_module(self): existing_l3_intf_facts = remove_empties_from_list(existing_l3_interfaces_facts) cmmnds = remove_empties_from_list(commands) - old_config = self.remove_default_entries(existing_l3_intf_facts) - cmds = self.remove_default_entries(cmmnds) + old_config = self.remove_default_entries(existing_l3_intf_facts, False) + cmds = self.remove_default_entries(cmmnds, True) new_config = get_new_config(cmds, old_config, TEST_KEYS_formatted_diff) new_config = remove_empties_from_list(new_config) - new_config = self.remove_default_entries(new_config) + new_config = self.remove_default_entries(new_config, False) result['after(generated)'] = new_config if self._module._diff: old_conf = remove_empties_from_list(old_config) - old_conf = self.remove_default_entries(old_conf) + old_conf = self.remove_default_entries(old_conf, False) new_conf = remove_empties_from_list(new_config) - new_conf = self.remove_default_entries(new_conf) + new_conf = self.remove_default_entries(new_conf, False) self.sort_lists_in_config(old_conf) self.sort_lists_in_config(new_conf) @@ -184,18 +184,23 @@ def _state_replaced(self, want, have): ret_requests = list() commands = list() new_want = self.update_object(want) - new_have = self.remove_default_entries(have) + new_have = self.remove_default_entries(have, False) get_replace_interfaces_list = self.get_interface_object_for_replaced(new_have, want) - diff = get_diff(get_replace_interfaces_list, new_want, TEST_KEYS) + diff_del = get_diff(get_replace_interfaces_list, new_want, TEST_KEYS) + diff_add = get_diff(new_want, get_replace_interfaces_list, TEST_KEYS) - if diff: - delete_l3_interfaces_requests = self.get_delete_all_requests(diff) + if diff_del: + delete_l3_interfaces_requests = self.get_delete_all_requests(diff_del) ret_requests.extend(delete_l3_interfaces_requests) - commands.extend(update_states(diff, "deleted")) - l3_interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want, have, want) + commands.extend(update_states(diff_del, "deleted")) + l3_interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want) ret_requests.extend(l3_interfaces_to_create_requests) commands.extend(update_states(want, "replaced")) + elif diff_add: + l3_interfaces_to_create_requests = self.get_create_l3_interfaces_requests(diff_add) + ret_requests.extend(l3_interfaces_to_create_requests) + commands.extend(update_states(diff_add, "replaced")) return commands, ret_requests def _state_overridden(self, want, have): @@ -210,9 +215,9 @@ def _state_overridden(self, want, have): commands = list() new_want = self.update_object(want) new_want = remove_empties_from_list(new_want) - new_want = self.remove_default_entries(new_want) + new_want = self.remove_default_entries(new_want, True) new_have = remove_empties_from_list(have) - new_have = self.remove_default_entries(new_have) + new_have = self.remove_default_entries(new_have, False) get_override_interfaces = self.get_interface_object_for_overridden(new_have) diff = get_diff(get_override_interfaces, new_want, TEST_KEYS) diff2 = get_diff(new_want, get_override_interfaces, TEST_KEYS) @@ -221,7 +226,7 @@ def _state_overridden(self, want, have): delete_interfaces_requests = self.get_delete_all_requests(have) ret_requests.extend(delete_interfaces_requests) commands.extend(update_states(diff, "deleted")) - interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want, have, want) + interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want) ret_requests.extend(interfaces_to_create_requests) commands.extend(update_states(want, "overridden")) @@ -236,7 +241,7 @@ def _state_merged(self, want, have, diff): """ self.validate_primary_ips(want) commands = diff - requests = self.get_create_l3_interfaces_requests(commands, have, want) + requests = self.get_create_l3_interfaces_requests(commands) if commands and len(requests) > 0: commands = update_states(commands, "merged") else: @@ -264,8 +269,9 @@ def _state_deleted(self, want, have): commands = update_states(commands, "deleted") return commands, requests - def remove_default_entries(self, config): + def remove_default_entries(self, config, input_cmds): new_config = list() + state = self._module.params['state'] for obj in config: new_obj = dict() if obj.get('ipv4', None) and \ @@ -274,8 +280,38 @@ def remove_default_entries(self, config): new_obj['ipv4'] = obj['ipv4'] if obj.get('ipv6', None) and \ (obj['ipv6'].get('addresses', None) or + obj['ipv6'].get('dad', None) or + obj['ipv6'].get('autoconf', None) is not None or obj['ipv6'].get('enabled', None) is not None): - new_obj['ipv6'] = obj['ipv6'] + new_obj['ipv6'] = obj['ipv6'].copy() + if new_obj['ipv6'].get('dad', None) == "DISABLE": + + # Because 'dad' is shown in the device IPv6 config as "DISABLE" when + # 'dad' configuration has been deleted, enable correct handling + # for all states by filtering out 'dad' == "DISABLE" unless one of the + # following conditions is true: + # + # (1) The 'config' parameter to this function represents input commands + # (from the executing user playbook) and the specified target state is a + # value other than 'deleted' ("positive" configuration). This is to + # enable idempotent handling for 'deleted' state when 'deleting' a 'dad' + # value of 'DISABLE' (a no-op that should not generate a request), while + # preserving the ability to 'merge' a 'dad' value of 'DISABLE' + # when that is requested by a playbook state of 'merged', 'overridden', + # or 'replaced'. + # + # or + # + # (2) The 'config' parameter to this function does not represent input + # commands (because it is from the current device configuration) and the + # specified target state is 'merged'. (This exclusion is to enable + # idempotent handling for 'merged' state when 'merging' a 'dad' value of + # 'DISABLE'.) + if ((input_cmds and state == "deleted") or + ((not input_cmds) and state != "merged")): + del new_obj['ipv6']['dad'] + if new_obj['ipv6'] == {}: + del new_obj['ipv6'] if new_obj: key_set = set(obj.keys()) @@ -308,9 +344,11 @@ def update_object(self, want): new_obj['ipv4'] = obj['ipv4'] if obj['ipv6'] is None: - new_obj['ipv6'] = {'addresses': None, 'enabled': False} + new_obj['ipv6'] = {'addresses': None, 'enabled': False, 'autoconf': False, 'dad': None} else: new_obj['ipv6'] = obj['ipv6'] + if new_obj['ipv6'].get('autoconf') is None: + new_obj['ipv6']['autoconf'] = False objects.append(new_obj) return objects @@ -329,9 +367,13 @@ def get_interface_object_for_overridden(self, have): if obj.get('ipv6', None): ipv6_addresses = obj['ipv6'].get('addresses', None) ipv6_enable = obj['ipv6'].get('enabled', None) + ipv6_autoconf = obj['ipv6'].get('autoconf', None) + ipv6_dad = obj['ipv6'].get('dad', None) else: ipv6_addresses = None ipv6_enable = None + ipv6_autoconf = None + ipv6_dad = None if ipv4_addresses is not None or ipv6_addresses is not None: objects.append(obj.copy()) @@ -359,6 +401,8 @@ def get_delete_l3_interfaces_requests(self, want, have): ipv4_addr_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/addresses/address={address}' ipv6_addr_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses/address={address}' ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/enabled' + ipv6_autoconf_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/ipv6_autoconfig' + ipv6_dad_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/ipv6_dad' if not want: return requests @@ -373,6 +417,8 @@ def get_delete_l3_interfaces_requests(self, want, have): have_ipv4_anycast_addrs = list() have_ipv6_addrs = list() have_ipv6_enabled = None + have_ipv6_autoconf = None + have_ipv6_dad = None if have_obj.get('ipv4'): if 'addresses' in have_obj['ipv4']: @@ -383,6 +429,11 @@ def get_delete_l3_interfaces_requests(self, want, have): have_ipv6_addrs = self.get_address('ipv6', [have_obj]) if have_obj.get('ipv6') and 'enabled' in have_obj['ipv6']: have_ipv6_enabled = have_obj['ipv6']['enabled'] + if have_obj.get('ipv6') and 'autoconf' in have_obj['ipv6']: + have_ipv6_autoconf = have_obj['ipv6']['autoconf'] + if (have_obj.get('ipv6') and 'dad' in have_obj['ipv6'] and + have_obj['ipv6']['dad'] is not None and have_obj['ipv6']['dad'] != "DISABLE"): + have_ipv6_dad = have_obj['ipv6']['dad'] ipv4 = l3.get('ipv4', None) ipv6 = l3.get('ipv6', None) @@ -397,7 +448,7 @@ def get_delete_l3_interfaces_requests(self, want, have): is_del_ipv6 = True elif ipv4 and not ipv4.get('addresses') and not ipv4.get('anycast_addresses'): is_del_ipv4 = True - elif ipv6 and not ipv6.get('addresses') and ipv6.get('enabled') is None: + elif ipv6 and not ipv6.get('addresses') and ipv6.get('enabled') is None and ipv6.get('autoconf') is None and ipv6.get('dad') is None: is_del_ipv6 = True if is_del_ipv4: @@ -450,14 +501,29 @@ def get_delete_l3_interfaces_requests(self, want, have): if have_ipv6_enabled: ipv6_enabled_delete_request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} requests.append(ipv6_enabled_delete_request) + + if have_ipv6_autoconf: + ipv6_autoconf_delete_request = {"path": ipv6_autoconf_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(ipv6_autoconf_delete_request) + + if have_ipv6_dad: + ipv6_dad_delete_request = {"path": ipv6_dad_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(ipv6_dad_delete_request) else: ipv6_addrs = [] ipv6_enabled = None + ipv6_autoconf = None + ipv6_dad = None if l3.get('ipv6'): if l3['ipv6'].get('addresses'): ipv6_addrs = l3['ipv6']['addresses'] if 'enabled' in l3['ipv6']: ipv6_enabled = l3['ipv6']['enabled'] + if 'autoconf' in l3['ipv6']: + ipv6_autoconf = l3['ipv6']['autoconf'] + if 'dad' in l3['ipv6']: + ipv6_dad = l3['ipv6']['dad'] + if ipv6_addrs: for ip in ipv6_addrs: if have_ipv6_addrs and ip['address'] in have_ipv6_addrs: @@ -468,6 +534,12 @@ def get_delete_l3_interfaces_requests(self, want, have): if have_ipv6_enabled and ipv6_enabled is not None: request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} requests.append(request) + if have_ipv6_autoconf and ipv6_autoconf is not None: + request = {"path": ipv6_autoconf_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(request) + if have_ipv6_dad and ipv6_dad == have_ipv6_dad: + request = {"path": ipv6_dad_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(request) return requests def get_delete_all_completely_requests(self, configs): @@ -485,6 +557,9 @@ def get_delete_all_requests(self, configs): ipv4_anycast_url += '/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway={anycast_ip}' ipv6_addrs_url_all = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses' ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/enabled' + ipv6_autoconf_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/ipv6_autoconfig' + ipv6_dad_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/ipv6_dad' + for l3 in configs: name = l3.get('name') ipv4_addrs = [] @@ -499,11 +574,17 @@ def get_delete_all_requests(self, configs): ipv6_addrs = [] ipv6_enabled = None + ipv6_autoconf = None + ipv6_dad = None if l3.get('ipv6'): if l3['ipv6'].get('addresses'): ipv6_addrs = l3['ipv6']['addresses'] if 'enabled' in l3['ipv6']: ipv6_enabled = l3['ipv6']['enabled'] + if 'autoconf' in l3['ipv6']: + ipv6_autoconf = l3['ipv6']['autoconf'] + if 'dad' in l3['ipv6'] and l3['ipv6']['dad'] is not None and l3['ipv6']['dad'] != "DISABLE": + ipv6_dad = l3['ipv6']['dad'] sub_intf = self.get_sub_interface_name(name) @@ -521,9 +602,15 @@ def get_delete_all_requests(self, configs): if ipv6_enabled: ipv6_enabled_delete_request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} requests.append(ipv6_enabled_delete_request) + if ipv6_autoconf: + ipv6_autoconf_delete_request = {"path": ipv6_autoconf_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(ipv6_autoconf_delete_request) + if ipv6_dad: + ipv6_dad_delete_request = {"path": ipv6_dad_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE} + requests.append(ipv6_dad_delete_request) return requests - def get_create_l3_interfaces_requests(self, configs, have, want): + def get_create_l3_interfaces_requests(self, configs): requests = [] if not configs: return requests @@ -533,6 +620,9 @@ def get_create_l3_interfaces_requests(self, configs, have, want): ipv4_anycast_url += 'openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway' ipv6_addrs_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses' ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config' + ipv6_autoconf_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config' + ipv6_eui64_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses' + ipv6_dad_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config' for l3 in configs: l3_interface_name = l3.get('name') @@ -551,11 +641,17 @@ def get_create_l3_interfaces_requests(self, configs, have, want): ipv6_addrs = [] ipv6_enabled = None + ipv6_autoconf = None + ipv6_dad = None if l3.get('ipv6'): if l3['ipv6'].get('addresses'): ipv6_addrs = l3['ipv6']['addresses'] if 'enabled' in l3['ipv6']: ipv6_enabled = l3['ipv6']['enabled'] + if 'autoconf' in l3['ipv6']: + ipv6_autoconf = l3['ipv6']['autoconf'] + if l3['ipv6'].get('dad'): + ipv6_dad = l3['ipv6']['dad'] if ipv4_addrs: ipv4_addrs_pri_payload = [] @@ -585,11 +681,13 @@ def get_create_l3_interfaces_requests(self, configs, have, want): if ipv6_addrs: ipv6_addrs_payload = [] + ipv6_addrs_eui64_payload = [] for item in ipv6_addrs: ipv6_addr_mask = item['address'].split('/') ipv6 = ipv6_addr_mask[0] ipv6_mask = ipv6_addr_mask[1] - ipv6_addrs_payload.append(self.build_create_addr_payload(ipv6, ipv6_mask)) + ipv6_eui64 = item.get('eui64') + ipv6_addrs_payload.append(self.build_create_addr_payload(ipv6, ipv6_mask, None, ipv6_eui64)) if ipv6_addrs_payload: payload = self.build_create_payload(ipv6_addrs_payload) ipv6_addrs_req = {"path": ipv6_addrs_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload} @@ -600,6 +698,16 @@ def get_create_l3_interfaces_requests(self, configs, have, want): ipv6_enabled_req = {"path": ipv6_enabled_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload} requests.append(ipv6_enabled_req) + if ipv6_autoconf is not None: + payload = self.build_update_ipv6_autoconf(ipv6_autoconf) + ipv6_autoconf_req = {"path": ipv6_autoconf_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload} + requests.append(ipv6_autoconf_req) + + if ipv6_dad is not None: + payload = self.build_update_ipv6_dad(ipv6_dad) + ipv6_dad_req = {"path": ipv6_dad_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload} + requests.append(ipv6_dad_req) + return requests def validate_primary_ips(self, want): @@ -624,10 +732,12 @@ def build_create_payload(self, addrs_payload): payload = {'openconfig-if-ip:addresses': {'address': addrs_payload}} return payload - def build_create_addr_payload(self, ip, mask, secondary=None): + def build_create_addr_payload(self, ip, mask, secondary=None, ipv6_eui64=None): cfg = {'ip': ip, 'prefix-length': float(mask)} if secondary: cfg['secondary'] = secondary + if ipv6_eui64: + cfg['openconfig-interfaces-private:eui64'] = ipv6_eui64 addr_payload = {'ip': ip, 'openconfig-if-ip:config': cfg} return addr_payload @@ -641,6 +751,14 @@ def build_update_ipv6_enabled(self, ipv6_enabled): payload = {'config': {'enabled': ipv6_enabled}} return payload + def build_update_ipv6_autoconf(self, ipv6_autoconf): + payload = {'config': {'ipv6_autoconfig': ipv6_autoconf}} + return payload + + def build_update_ipv6_dad(self, ipv6_dad): + payload = {'config': {'ipv6_dad': ipv6_dad}} + return payload + def sort_lists_in_config(self, config): if config: config.sort(key=lambda x: x['name']) diff --git a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py index 7ccd8ce02..f36ac2dbc 100644 --- a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py @@ -61,7 +61,6 @@ ERR_MSG = to_native(e) LIB_IMP_ERR = traceback.format_exc() - PUT = 'put' PATCH = 'patch' DELETE = 'delete' @@ -170,6 +169,11 @@ def set_state(self, want, have): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + state = self._module.params['state'] + + self.validate_want(want, state) + self.preprocess_want(want, state) + commands = [] diff = get_diff(want, have, TEST_KEYS) if diff: @@ -178,10 +182,6 @@ def set_state(self, want, have): diff_members = [] diff_portchannels = [] - state = self._module.params['state'] - if state in ('overridden', 'merged', 'replaced') and not want: - self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) - if state == 'overridden': commands, requests = self._state_overridden(want, have, diff_members, diff_portchannels) elif state == 'deleted': @@ -214,6 +214,21 @@ def _state_replaced(self, want, have, diff_members, diff_portchannels): requests = self.get_delete_lag_interfaces_requests(replaced_list) if requests: commands.extend(update_states(replaced_list, "deleted")) + + es_commands, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, + want, have, True) + if es_requests: + es_cmds = list() + for cmd in es_commands: + po_name = cmd['name'] + cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) + if not cmd_in_cmds: + es_cmds.append(cmd) + if es_cmds: + commands.extend(update_states(es_cmds, "deleted")) + + requests.extend(es_requests) + replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced") if replaced_requests: commands.extend(replaced_commands) @@ -241,6 +256,10 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): replaced_list.append(list_obj) requests = self.get_delete_lag_interfaces_requests(replaced_list) + + nu, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, + want, have, True) + requests.extend(es_requests) commands.extend(update_states(replaced_list, "deleted")) deleted_po_list = list() @@ -249,6 +268,9 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): if not list_obj: deleted_po_list.append(i) + nu, es_requests = self.get_delete_po_ethernet_segment_requests(deleted_po_list, + have) + requests.extend(es_requests) requests_deleted_po = self.get_delete_portchannel_requests(deleted_po_list) requests.extend(requests_deleted_po) commands_del = self.prune_commands(deleted_po_list) @@ -267,7 +289,9 @@ def _state_merged(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to merge the provided into the current configuration """ - return self.template_for_lag_creation(have, diff_members, diff_portchannels, "merged") + commands, requests = self.template_for_lag_creation(have, diff_members, + diff_portchannels, "merged") + return commands, requests def _state_deleted(self, want, have, diff): """ The command generator when state is deleted @@ -287,15 +311,26 @@ def _state_deleted(self, want, have, diff): commands_del = self.prune_commands(have) commands.extend(update_states(commands_del, "deleted")) else: # delete specific lag interfaces and specific portchannels - commands = get_diff(want, diff, TEST_KEYS) - commands = remove_empties_from_list(commands) - want_members, want_portchannels = self.diff_list_for_member_creation(commands) - commands, requests = self.template_for_lag_deletion(have, want_members, want_portchannels, "deleted") + po_commands = get_diff(want, diff, TEST_KEYS) + po_commands = remove_empties_from_list(po_commands) + want_members, want_portchannels = self.diff_list_for_member_creation(po_commands) + + del_commands, del_requests = self.template_for_lag_deletion(want, have, want_members, + want_portchannels, "deleted") + if del_commands: + commands.extend(del_commands) + if del_requests: + requests.extend(del_requests) return commands, requests def diff_list_for_member_creation(self, diff): - diff_members = [x for x in diff if "members" in x.keys()] - diff_portchannels = [x for x in diff if ("name" in x.keys() and "members" not in x.keys())] + diff_members = list() + diff_portchannels = list() + for x in diff: + if "members" in x.keys() or "ethernet_segment" in x.keys(): + diff_members.append(x) + else: + diff_portchannels.append(x) return diff_members, diff_portchannels def template_for_lag_creation(self, have, diff_members, diff_portchannels, state_name): @@ -309,26 +344,32 @@ def template_for_lag_creation(self, have, diff_members, diff_portchannels, state po_list = [] if po_list: commands.extend(update_states(po_list, state_name)) - diff_members_remove_none = [x for x in diff_members if x["members"]] + diff_members_remove_none = [x for x in diff_members if x.get("members")] if diff_members_remove_none: request = self.create_lag_interfaces_requests(diff_members_remove_none) if request: requests.extend(request) else: requests = request + + es_commands, es_request = self.create_ethernet_segment_requests(diff_members, have) + if es_request: + requests.append(es_request) + commands.extend(update_states(diff_members, state_name)) if diff_portchannels: portchannels, po_requests = self.call_create_port_channel(diff_portchannels, have) requests.extend(po_requests) + commands.extend(update_states(portchannels, state_name)) return commands, requests - def template_for_lag_deletion(self, have, delete_members, delete_portchannels, state_name): + def template_for_lag_deletion(self, want, have, delete_members, delete_portchannels, state_name): commands = list() requests = list() portchannel_requests = list() if delete_members: - delete_members_remove_none = [x for x in delete_members if x["members"]] + delete_members_remove_none = [x for x in delete_members if "members" in x.keys() and x["members"]] requests = self.get_delete_lag_interfaces_requests(delete_members_remove_none) delete_all_members = [x for x in delete_members if "members" in x.keys() and not x["members"]] delete_all_list = list() @@ -347,7 +388,26 @@ def template_for_lag_deletion(self, have, delete_members, delete_portchannels, s requests = deleteall_requests if requests: commands.extend(update_states(delete_members, state_name)) + + es_commands, es_requests = self.get_delete_ethernet_segment_requests(delete_members, + want, have, False) + if es_requests: + es_cmds = list() + for cmd in es_commands: + po_name = cmd['name'] + cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) + if not cmd_in_cmds: + es_cmds.append(cmd) + if es_cmds: + commands.extend(update_states(es_cmds, state_name)) + requests.extend(es_requests) + if delete_portchannels: + nu, es_requests = self.get_delete_po_ethernet_segment_requests(delete_portchannels, + have) + if es_requests: + requests.extend(es_requests) + portchannel_requests = self.get_delete_portchannel_requests(delete_portchannels) commands_del = self.prune_commands(delete_portchannels) commands.extend(update_states(commands_del, state_name)) @@ -372,6 +432,71 @@ def create_lag_interfaces_requests(self, commands): requests.append(request) return requests + def create_ethernet_segment_requests(self, diff_members, have): + es_commands = [] + es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' + es_payload_list = list() + + for cmd in diff_members: + po_name = cmd['name'] + cmd_es = cmd.get('ethernet_segment', None) + if cmd_es: + es = dict() + have_po = next((po for po in have if po['name'] == po_name), {}) + have_es = have_po.get('ethernet_segment', {}) + + if cmd_es.get('esi_type'): + esi_type = cmd_es['esi_type'] + else: + esi_type = have_es['esi_type'] + + esi = cmd_es.get('esi') + if esi_type == 'auto_lacp': + esi_t = 'TYPE_1_LACP_BASED' + esi = 'AUTO' + elif esi_type == 'auto_system_mac': + esi_t = 'TYPE_3_MAC_BASED' + esi = 'AUTO' + elif esi_type == 'ethernet_segment_id': + esi_t = 'TYPE_0_OPERATOR_CONFIGURED' + if not esi: + esi = have_es['esi'] + esi = ''.join(esi.split(':')) + + if cmd_es.get('df_preference'): + df_pref = cmd_es['df_preference'] + else: + df_pref = None + + es_payload_item = { + 'name': po_name, + 'config': { + 'name': po_name, + 'esi-type' : esi_t, + 'esi' : esi, + 'interface': po_name + } + } + + if df_pref: + df_election = {'config': {'preference': df_pref}} + es_payload_item['df-election'] = df_election + + es_payload_list.append(es_payload_item) + es_commands.append(cmd) + + es_request = {} + if es_payload_list: + es_payload = { + 'openconfig-network-instance:ethernet-segments': { + 'ethernet-segment': es_payload_list + } + } + + es_request = {'path': es_path, 'method': PATCH, 'data': es_payload} + + return es_commands, es_request + def build_create_payload_member(self, name): payload_template = """{\n"openconfig-if-aggregate:aggregate-id": "{{name}}"\n}""" input_data = {"name": name} @@ -409,6 +534,7 @@ def call_create_port_channel(self, commands, have): if not any(d['name'] == c['name'] for d in have): commands_list.append(c) requests = self.create_port_channel(commands_list) + return commands_list, requests def get_delete_all_lag_interfaces_requests(self): @@ -445,6 +571,73 @@ def get_delete_lag_interfaces_requests(self, commands): return requests + def get_delete_po_ethernet_segment_requests(self, delete_portchannels, have): + es_commands = [] + es_requests = [] + es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' + ess_path = es_path + '/ethernet-segment=%s' + + for cmd in delete_portchannels: + po_name = cmd['name'] + have_po = next((po for po in have if po['name'] == po_name), {}) + have_es = have_po.get('ethernet_segment', {}) + if have_es: + ed_ess_path = ess_path % quote(po_name, safe='') + es_request = {'path': ed_ess_path, 'method': DELETE} + es_requests.append(es_request) + + es_commands.append(cmd) + + return es_commands, es_requests + + def get_delete_ethernet_segment_requests(self, delete_members, want, have, delete_es=False): + es_commands = [] + es_requests = [] + es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' + ess_path = es_path + '/ethernet-segment=%s' + df_pref_path = es_path + '/ethernet-segment=%s/df-election/config/preference' + + for cmd in delete_members: + po_name = cmd['name'] + cmd_es = cmd.get('ethernet_segment', None) + if cmd_es: + have_po = next((po for po in have if po['name'] == po_name), {}) + have_es = have_po.get('ethernet_segment', {}) + if not have_es: + continue + + want_po = next((po for po in want if po['name'] == po_name), {}) + want_es = want_po.get('ethernet_segment', {}) + + del_es = False + del_df_pref = False + if delete_es: + del_es = True + else: + if cmd_es.get('esi_type'): + if cmd_es.get('esi'): + if cmd_es.get('df_preference') or not want_es.get('df_preference'): + del_es = True + elif not want_es.get('esi'): + if cmd_es.get('df_preference'): + if have_es.get('df_preference'): + del_df_pref = True + elif not want_es.get('df_preference'): + del_es = True + + if del_es: + ed_ess_path = ess_path % quote(po_name, safe='') + es_request = {'path': ed_ess_path, 'method': DELETE} + es_requests.append(es_request) + elif del_df_pref: + ed_df_pref_path = df_pref_path % quote(po_name, safe='') + df_pref_request = {'path': ed_df_pref_path, 'method': DELETE} + es_requests.append(df_pref_request) + + es_commands.append(cmd) + + return es_commands, es_requests + def get_delete_portchannel_requests(self, commands): requests = [] # Create URL and payload @@ -473,4 +666,36 @@ def prune_commands(self, commands): cmds = deepcopy(commands) for cmd in cmds: cmd.pop('members', None) + cmd.pop('ethernet_segment', None) return cmds + + def validate_want(self, want, state): + if not want: + if state in ('overridden', 'merged', 'replaced'): + self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) + + return + for conf in want: + es = conf.get('ethernet_segment', None) + if es: + esi = es.get('esi', None) + if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: + if esi and esi != 'AUTO': + self._module.fail_json(msg='value of esi must be "AUTO" for esi_type {0}'.format(es['esi_type'])) + else: + if not esi and state != 'deleted': + self._module.fail_json(msg='value of esi must be provided for esi_type {0}'.format(es['esi_type'])) + + df_pref = es.get('df_preference') + if df_pref and (df_pref < 1 or df_pref > 65535): + self._module.fail_json(msg='value of df_preference must be in range 1..65535') + + def preprocess_want(self, want, state): + if not want: + return + for conf in want: + es = conf.get('ethernet_segment', None) + if es: + if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: + if state != 'deleted': + es['esi'] = 'AUTO' diff --git a/plugins/module_utils/network/sonic/config/ldap/__init__.py b/plugins/module_utils/network/sonic/config/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/ldap/ldap.py b/plugins/module_utils/network/sonic/config/ldap/ldap.py new file mode 100644 index 000000000..3521a4337 --- /dev/null +++ b/plugins/module_utils/network/sonic/config/ldap/ldap.py @@ -0,0 +1,805 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_ldap class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + validate_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + update_states, + get_diff, + remove_matching_defaults, + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + get_new_config, + get_formatted_config_diff, + __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF, + __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF +) +from ansible.module_utils.connection import ConnectionError + +PATCH = 'patch' +DELETE = 'delete' +modName = 'openconfig-aaa-ldap-ext:ldap' + +TEST_KEYS = [ + {'config': {'name': ''}}, + {'servers': {'address': ''}}, + {'attribute': {'from': ''}}, + {'default_attribute': {'from': ''}}, + {'objectclass': {'from': ''}}, + {'override_attribute': {'from': ''}}, + {'map_remote_groups_to_sonic_roles': {'remote_group': ''}} +] + +TEST_KEYS_formatted_diff = [ + {'config': {'name': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'servers': {'address': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'attribute': {'from': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'default_attribute': {'from': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'objectclass': {'from': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'override_attribute': {'from': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'map_remote_groups_to_sonic_roles': {'remote_group': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, +] + +LDAP_GROUPS = {'global': 'LDAP', 'nss': 'LDAP_NSS', 'pam': 'LDAP_PAM', 'sudo': 'LDAP_SUDO'} + +COMMON_ATTRIBUTES = ['base', 'bind_timelimit', 'binddn', 'bindpw', 'port', 'ssl', 'retry', 'version', 'timelimit'] + +MAP_ATTRIBUTES = ['attribute', 'default_attribute', 'objectclass', 'override_attribute', 'map_remote_groups_to_sonic_roles'] +SERVER_ATTRIBUTES = ['address', 'server_type', 'port', 'ssl', 'priority', 'retry'] + +ONLY_NSS_ATTRIBUTES = ['nss_base_group', 'nss_base_netgroup', 'nss_base_passwd', + 'nss_base_shadow', 'nss_base_sudoers', 'nss_initgroups_ignoreusers'] +ONLY_PAM_ATTRIBUTES = ['pam_filter', 'pam_group_dn', 'pam_login_attribute', 'pam_member_attribute'] + +GLOBAL_ATTRIBUTES = COMMON_ATTRIBUTES + ['servers', 'idle_timelimit', 'nss_skipmembers', 'map', 'source_interface', 'scope', 'sudoers_base', + 'sudoers_search_filter', 'vrf'] + ONLY_NSS_ATTRIBUTES + ONLY_PAM_ATTRIBUTES +NSS_ATTRIBUTES = COMMON_ATTRIBUTES + ONLY_NSS_ATTRIBUTES + ['idle_timelimit', 'scope'] +PAM_ATTRIBUTES = COMMON_ATTRIBUTES + ONLY_PAM_ATTRIBUTES + ['nss_base_passwd', 'scope'] +SUDO_ATTRIBUTES = COMMON_ATTRIBUTES + ['sudoers_base', 'sudoers_search_filter'] + +ATTRIBUTES = {'global': GLOBAL_ATTRIBUTES, 'nss': NSS_ATTRIBUTES, 'pam': PAM_ATTRIBUTES, 'sudo': SUDO_ATTRIBUTES} + +default_entries = [ + [ + {'name': 'bind_timelimit', 'default': 10} + ], + [ + {'name': 'port', 'default': 389} + ], + [ + {'name': 'bindpw'}, + {'name': 'encrypted', 'default': False} + ], + [ + {'name': 'idle_timelimit', 'default': 0} + ], + [ + {'name': 'nss_skipmembers', 'default': False} + ], + [ + {'name': 'servers'}, + {'name': 'server_type', 'default': 'all'} + ], + [ + {'name': 'servers'}, + {'name': 'ssl', 'default': 'off'} + ], + [ + {'name': 'servers'}, + {'name': 'priority', 'default': 1} + ], + [ + {'name': 'servers'}, + {'name': 'retry', 'default': 0} + ], + [ + {'name': 'retry', 'default': 0} + ], + [ + {'name': 'scope', 'default': 'sub'} + ], + [ + {'name': 'ssl', 'default': 'off'} + ], + [ + {'name': 'timelimit', 'default': 0} + ], + [ + {'name': 'version', 'default': 3} + ] +] + +base_url = 'data/openconfig-system:system/aaa/server-groups/server-group={name}' + +CONFIG_ATTRIBUTES = { + 'base': 'base', + 'binddn': 'bind-dn', + 'bindpw': 'bind-pw', + 'bind_timelimit': 'bind-time-limit', + 'port': 'port', + 'ssl': 'ssl', + 'timelimit': 'search-time-limit', + 'retry': 'retransmit-attempts', + 'version': 'version' +} + +ONLY_NSS_ATTRIBUTES = { + 'nss_base_group': 'nss-base-group', + 'nss_base_netgroup': 'nss-base-netgroup', + 'nss_base_passwd': 'nss-base-passwd', + 'nss_base_shadow': 'nss-base-shadow', + 'nss_base_sudoers': 'nss-base-sudoers', + 'nss_initgroups_ignoreusers': 'nss-initgroups-ignoreusers', + 'scope': 'scope', + 'idle_timelimit': 'idle-time-limit' +} + +ONLY_PAM_ATTRIBUTES = { + 'pam_filter': 'pam-filter', + 'pam_group_dn': 'pam-group-dn', + 'pam_login_attribute': 'pam-login-attribute', + 'pam_member_attribute': 'pam-member-attribute', + 'scope': 'scope', + 'nss_base_passwd': 'nss-base-passwd' +} + + +ONLY_SUDO_ATTRIBUTES = { + 'sudoers_base': 'sudoers-base', + 'sudoers_search_filter': 'sudoers-search-filter' +} + +MAP_ATTRIBUTES = { + 'attribute': 'ATTRIBUTE', + 'default_attribute': 'DEFAULT_ATTRIBUTE_VALUE', + 'objectclass': 'OBJECTCLASS', + 'override_attribute': 'OVERRIDE_ATTRIBUTE_VALUE', + 'map_remote_groups_to_sonic_roles': 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' +} + +SERVER_ATTRIBUTES = { + 'server_type': 'use-type', + 'port': 'port', + 'ssl': 'ssl', + 'priority': 'priority', + 'retry': 'retransmit-attempts' +} + + +class Ldap(ConfigBase): + """ + The sonic_ldap class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ldap', + ] + + def __init__(self, module): + super(Ldap, self).__init__(module) + + def get_ldap_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + ldap_facts = facts['ansible_network_resources'].get('ldap') + if not ldap_facts: + return [] + return ldap_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + + existing_ldap_facts = self.get_ldap_facts() + commands, requests = self.set_config(existing_ldap_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + + result['before'] = existing_ldap_facts + old_config = existing_ldap_facts + + if self._module.check_mode: + existing_ldap_facts = remove_empties_from_list(existing_ldap_facts) + new_config = self._get_generated_config(commands, existing_ldap_facts, self._module.params['state']) + self.sort_lists_in_config(new_config) + result['after(generated)'] = new_config + else: + changed_ldap_facts = self.get_ldap_facts() + new_config = changed_ldap_facts + if result['changed']: + result['after'] = changed_ldap_facts + + if self._module._diff: + self.sort_lists_in_config(new_config) + self.sort_lists_in_config(old_config) + result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + + result['commands'] = commands + result['warnings'] = warnings + return result + + def set_config(self, existing_ldap_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + if want: + want = remove_empties_from_list(want) + want = self.validate_and_normalize_config(want) + else: + want = [] + + have = existing_ldap_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + state = self._module.params['state'] + if state == 'overridden' or state == 'replaced': + commands, requests = self._state_replaced_or_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + return commands, requests + + def _state_replaced_or_overridden(self, want, have): + """ The command generator when state is replaced or overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + self.sort_lists_in_config(want) + self.sort_lists_in_config(have) + add_config, del_config = self._get_replaced_overridden_config(want, have) + if del_config: + del_commands, del_requests = self.get_delete_ldap_requests(del_config, have, False) + if del_commands and len(del_requests) > 0: + commands.extend(update_states(del_config, 'deleted')) + requests.extend(del_requests) + + if add_config: + mod_requests = self.get_create_ldap_requests(add_config) + if len(mod_requests) > 0: + commands.extend(update_states(add_config, self._module.params['state'])) + requests.extend(mod_requests) + + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = get_diff(want, have) + requests = self.get_create_ldap_requests(commands) + + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands , requests = [], [] + is_delete_all = False + + if not want: + commands = have + new_have = have + is_delete_all = True + else: + self.sort_lists_in_config(want) + self.sort_lists_in_config(have) + new_want = deepcopy(want) + new_have = deepcopy(have) + for default_entry in default_entries: + remove_matching_defaults(new_have, default_entry) + + new_have = remove_empties_from_list(new_have) + commands = new_want + + commands, requests = self.get_delete_ldap_requests(commands, new_have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') + else: + commands = [] + + return commands, requests + + def _get_replaced_overridden_config(self, want, have): + add_config, del_config = [], [] + for conf in want: + name = conf.get('name') + have_conf = next((item for item in have if item['name'] == name), None) + + if not have_conf: + add_config.append(conf) + else: + add_cfg, del_cfg = {}, {} + for attr in ATTRIBUTES[name]: + if attr in conf: + if attr not in have_conf: + add_cfg[attr] = conf[attr] + elif conf[attr] != have_conf[attr]: + if attr not in ('name', 'servers', 'map'): + add_cfg[attr] = conf[attr] + del_cfg[attr] = have_conf[attr] + elif attr == 'servers': + add_srv, del_srv = [], [] + for server in conf[attr]: + address = server.get('address') + match_server = next((item for item in have_conf[attr] if item['address'] == address), None) + if not match_server: + add_srv.append(server) + else: + add_srv_cfg = {} + del_srv_cfg = {} + for srv_attr in SERVER_ATTRIBUTES: + if srv_attr != 'address': + if srv_attr in server: + if srv_attr not in match_server: + add_srv_cfg[srv_attr] = server[srv_attr] + elif server[srv_attr] != match_server[srv_attr]: + add_srv_cfg[srv_attr] = server[srv_attr] + del_srv_cfg[srv_attr] = match_server[srv_attr] + elif srv_attr in match_server: + del_srv_cfg[srv_attr] = match_server[srv_attr] + if add_srv_cfg: + add_srv_cfg['address'] = address + add_srv.append(add_srv_cfg) + if del_srv_cfg: + del_srv_cfg['address'] = address + if del_srv_cfg != match_server: + del_srv.append(del_srv_cfg) + else: + del_srv.append({'address': address}) + for server in have_conf[attr]: + address = server.get('address') + match_server = next((item for item in conf[attr] if item['address'] == address), None) + if not match_server: + del_srv.append({'address': address}) + if add_srv: + add_cfg['servers'] = add_srv + if del_srv: + del_cfg['servers'] = del_srv + elif attr == 'map': + add_map, del_map = {}, {} + for map_attr in MAP_ATTRIBUTES: + if map_attr in conf[attr]: + if map_attr not in have_conf[attr]: + add_map[map_attr] = conf[attr][map_attr] + elif conf[attr][map_attr] != have_conf[attr][map_attr]: + if map_attr != 'map_remote_groups_to_sonic_roles': + add_map_list, del_map_list = [], [] + for map in conf[attr][map_attr]: + match_map = next((item for item in have_conf[attr][map_attr] if item['from'] == map['from']), None) + if not match_map: + add_map_list.append(map) + elif map['to'] != match_map['to']: + add_map_list.append(map) + del_map_list.append(match_map) + for map in have_conf[attr][map_attr]: + match_map = next((item for item in conf[attr][map_attr] if item['from'] == map['from']), None) + if not match_map: + del_map_list.append(map) + if add_map_list: + add_map[map_attr] = add_map_list + if del_map_list: + del_map[map_attr] = del_map_list + else: + add_map_list, del_map_list = [], [] + for map in conf[attr][map_attr]: + match_map = next((i for i in have_conf[attr][map_attr] if i['remote_group'] == map['remote_group']), None) + if not match_map: + add_map_list.append(map) + else: + added_roles = set(map['sonic_roles']) - set(match_map['sonic_roles']) + removed_roles = set(match_map['sonic_roles']) - set(map['sonic_roles']) + common = set(match_map['sonic_roles']) & set(map['sonic_roles']) + if added_roles or common: + roles = list(added_roles) + list(common) + add_map_list.append({'remote_group': map['remote_group'], 'sonic_roles': roles}) + if removed_roles: + roles = list(removed_roles) + list(common) + del_map_list.append({'remote_group': map['remote_group'], 'sonic_roles': roles}) + for map in have_conf[attr][map_attr]: + match_map = next((i for i in conf[attr][map_attr] if i['remote_group'] == map['remote_group']), None) + if not match_map: + del_map_list.append({'remote_group': map['remote_group']}) + if add_map_list: + add_map[map_attr] = add_map_list + if del_map_list: + del_map[map_attr] = del_map_list + elif map_attr in have_conf[attr]: + del_map[map_attr] = have_conf[attr][map_attr] + if add_map: + add_cfg[attr] = add_map + if del_map: + del_cfg[attr] = del_map + elif attr in have_conf: + del_cfg[attr] = have_conf[attr] + if add_cfg: + add_cfg['name'] = name + add_config.append(add_cfg) + if del_cfg: + del_cfg['name'] = name + del_config.append(del_cfg) + + if self._module.params['state'] == 'overridden': + for conf in have: + name = conf.get('name') + want_conf = next((item for item in want if item['name'] == name), None) + + if not want_conf: + del_config.append({'name': name}) + + return add_config, del_config + + def get_create_ldap_requests(self, commands): + requests = [] + + if not commands: + return requests + + for cmd in commands: + payload = {'openconfig-system:server-group': []} + payload_attr = {} + name = cmd.get('name', None) + for attr in cmd: + if cmd.get(attr) is not None: + if attr not in ('name', 'bindpw', 'source_interface', 'map', 'servers'): + attribute = CONFIG_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_NSS_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_PAM_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_SUDO_ATTRIBUTES.get(attr) + if name == 'global': + ATTRIBUTES = { + 'vrf': 'vrf-name', + 'nss_skipmembers': 'nss-skipmembers' + } + attribute = attribute or ATTRIBUTES.get(attr) + if attribute: + payload_attr.setdefault(modName, {}) + payload_attr[modName].setdefault('config', {}) + value = cmd[attr].upper() if attribute in ('ssl', 'scope') else cmd[attr] + payload_attr[modName]['config'][attribute] = value + elif attr == 'bindpw': + bindpw = cmd.get('bindpw') + if bindpw: + pwd = cmd['bindpw'].get('pwd') + is_encrypted = cmd['bindpw'].get('encrypted') + if pwd is not None: + payload_attr.setdefault(modName, {}) + payload_attr[modName].setdefault('config', {}) + payload_attr[modName]['config']['bind-pw'] = pwd + if is_encrypted is not None: + payload_attr[modName]['config']['encrypted'] = is_encrypted + elif attr == 'source_interface': + payload_attr.setdefault('config', {}) + payload_attr['config']['source-interface'] = cmd['source_interface'] + elif attr == 'map': + payload_attr.setdefault(modName, {}) + payload_attr[modName].setdefault('maps', {}) + payload_attr[modName]['maps'].setdefault('map', []) + + if cmd.get('map'): + map_payload_list = [] + for map_attributes in cmd['map']: + for map_attr in cmd['map'].get(map_attributes, []): + map_payload = {} + if map_attributes != 'map_remote_groups_to_sonic_roles': + from_val = map_attr.get('from') + to_val = map_attr.get('to') + if from_val is not None and to_val is not None: + map_payload = { + 'config': { + 'to': to_val + }, + 'from': from_val, + 'name': MAP_ATTRIBUTES[map_attributes] + } + else: + sonic_roles = map_attr.get('sonic_roles', []) + sonic_roles_list = [] + for role in sonic_roles: + sonic_roles_list.append(role) + remote_group = map_attr.get('remote_group', '') + if sonic_roles and remote_group: + map_payload = { + 'config': { + 'to': ','.join(sonic_roles_list), + }, + 'from': remote_group, + 'name': MAP_ATTRIBUTES[map_attributes] + } + if map_payload: + map_payload_list.append(map_payload) + if map_payload_list: + payload_attr[modName]['maps']['map'] = map_payload_list + elif attr == 'servers': + payload_attr.setdefault('servers', {}) + payload_attr['servers'].setdefault('server', []) + + server_payload_list = [] + + for server in cmd['servers']: + server_payload = {} + for server_attr in server: + if server_attr == 'address': + server_payload['address'] = server['address'] + server_payload['config'] = {'address': server['address']} + else: + server_payload.setdefault('openconfig-aaa-ldap-ext:ldap', {}) + server_payload['openconfig-aaa-ldap-ext:ldap'].setdefault('config', {}) + server_attr_val = server[server_attr] + if server_attr in ('ssl', 'server_type'): + server_attr_val = server_attr_val.upper() + server_payload['openconfig-aaa-ldap-ext:ldap']['config'][SERVER_ATTRIBUTES[server_attr]] = server_attr_val + if server_payload: + server_payload_list.append(server_payload) + if server_payload_list: + payload_attr['servers']['server'] = server_payload_list + + if payload_attr: + payload_attr['name'] = LDAP_GROUPS[name] + payload_attr.setdefault('config', {}) + payload_attr['config']['name'] = LDAP_GROUPS[name] + payload['openconfig-system:server-group'].append(payload_attr) + requests.append({'path': base_url.format(name=LDAP_GROUPS[name]), 'method': PATCH, 'data': payload}) + return requests + + def get_delete_ldap_requests(self, commands, have, is_delete_all): + commands_del, requests = [], [] + + for conf in commands: + delete_cmd = {} + name = conf.get('name') + have_conf = next((item for item in have if item['name'] == name), None) + if have_conf: + if len(conf) == 1 or is_delete_all or have_conf == conf: + # Delete the LDAP server type configuration completely + delete_cmd = {'name': name} + commands_del.append(delete_cmd) + url = base_url.format(name=LDAP_GROUPS[name]) + requests.append({'path': url, 'method': DELETE}) + continue + + for attr in conf: + if attr not in ('name', 'bindpw', 'source_interface', 'map', 'servers'): + if have_conf.get(attr): + attribute = CONFIG_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_NSS_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_PAM_ATTRIBUTES.get(attr) + attribute = attribute or ONLY_SUDO_ATTRIBUTES.get(attr) + if name == 'global': + ATTRIBUTES = { + 'vrf': 'vrf-name', + 'nss_skipmembers': 'nss-skipmembers' + } + attribute = attribute or ATTRIBUTES.get(attr) + if attribute and have_conf[attr] == conf[attr]: + delete_cmd[attr] = conf[attr] + url = base_url.format(name=LDAP_GROUPS[name]) + '/%s/config/%s' % (modName, attribute) + requests.append({'path': url, 'method': DELETE}) + elif attr == 'bindpw': + if have_conf.get(attr) is not None and have_conf[attr] == conf[attr]: + delete_cmd[attr] = conf[attr] + url = base_url.format(name=LDAP_GROUPS[name]) + '/%s/config/%s' % (modName, 'bind-pw') + requests.append({'path': url, 'method': DELETE}) + elif attr == 'source_interface': + if have_conf.get(attr) is not None and have_conf[attr] == conf[attr]: + delete_cmd[attr] = conf[attr] + url = base_url.format(name=LDAP_GROUPS[name]) + '/config/%s' % ('source-interface') + requests.append({'path': url, 'method': DELETE}) + elif attr == 'map': + map_cmd, map_requests = self.get_delete_ldap_map_request(name, conf.get(attr), have_conf.get(attr)) + if map_cmd and map_requests: + delete_cmd['map'] = map_cmd + requests.extend(map_requests) + elif attr == 'servers': + server_cmd, server_requests = self.get_delete_ldap_servers_request(name, conf.get(attr), have_conf.get(attr), is_delete_all) + if server_cmd and server_requests: + delete_cmd['servers'] = server_cmd + requests.extend(server_requests) + + if delete_cmd: + delete_cmd['name'] = name + commands_del.append(delete_cmd) + + return commands_del, requests + + def get_delete_ldap_map_request(self, name, conf, have_conf): + commands, requests = {}, [] + + if not conf: + return commands, requests + + for attr in conf: + map_attr = [] + if conf.get(attr) and attr in have_conf and have_conf.get(attr): + for map_list in conf[attr]: + from_val, match = None, None + if attr != 'map_remote_groups_to_sonic_roles': + from_val = map_list.get('from') + match = next((item for item in have_conf[attr] if item['from'] == from_val), None) + else: + from_val = map_list.get('remote_group') + match = next((item for item in have_conf[attr] if item['remote_group'] == from_val), None) + if from_val is not None and match is not None: + map_attr.append(match) + from_val = from_val.replace('\\\\', '\\').replace('/', '%2F') + url = base_url.format(name=LDAP_GROUPS[name]) + '/%s/maps/map=%s,%s' % (modName, MAP_ATTRIBUTES[attr], from_val) + requests.append({'path': url, 'method': DELETE}) + if map_attr: + commands[attr] = map_attr + return commands, requests + + def get_delete_ldap_servers_request(self, name, conf, have_conf, is_delete_all): + commands, requests = [], [] + + if not conf: + return commands, requests + + for server in conf: + server_attr = {} + address = server.get('address') + if address: + match = next((item for item in have_conf if item['address'] == address), None) + if match: + if len(server) == 1 or is_delete_all: + server_attr = {'address': address} + url = base_url.format(name=LDAP_GROUPS[name]) + '/servers/server=%s' % (address) + requests.append({'path': url, 'method': DELETE}) + else: + for attr in server: + if attr != 'address': + if server.get(attr) and match.get(attr) and server[attr] == match[attr]: + server_attr[attr] = server[attr] + url = base_url.format(name=LDAP_GROUPS[name]) + url = url + '/servers/server=%s/%s/config/%s' % (address, modName, SERVER_ATTRIBUTES[attr]) + requests.append({'path': url, 'method': DELETE}) + if server_attr: + server_attr['address'] = address + commands.append(server_attr) + return commands, requests + + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: x.get('name')) + for cfg in config: + if cfg.get('servers'): + cfg['servers'].sort(key=lambda x: x['address']) + if cfg.get('map'): + if cfg['map'].get('attributes'): + cfg['map']['attributes'].sort(key=lambda x: x['from']) + if cfg['map'].get('default_attribute'): + cfg['map']['default_attribute'].sort(key=lambda x: x['from']) + if cfg['map'].get('objectclass'): + cfg['map']['objectclass'].sort(key=lambda x: x['from']) + if cfg['map'].get('override_attribute'): + cfg['map']['override_attribute'].sort(key=lambda x: x['from']) + if cfg['map'].get('map_remote_groups_to_sonic_roles'): + cfg['map']['map_remote_groups_to_sonic_roles'].sort(key=lambda x: x['remote_group']) + for group in cfg['map']['map_remote_groups_to_sonic_roles']: + if group.get('sonic_roles'): + group['sonic_roles'].sort() + + def validate_and_normalize_config(self, config_list): + updated_config_list = [] + + # To ensure that the configuring attribute belongs only to those groups that are supported + # For example, 'sudoers_search_filter' only supported in 'global' and 'sudo'. + # Hence, for 'nss' and 'pam', this attr is ignored by this function + for config in config_list: + cfg = {} + name = config.get('name') + for attr in config: + if attr == 'name': + continue + elif attr in ATTRIBUTES[name]: + cfg[attr] = config[attr] + else: + self._module.fail_json(msg='The {0} attribute is not supported for the LDAP {1} type.'.format(attr, name)) + if cfg or len(config) == 1: + cfg['name'] = name + updated_config_list.append(cfg) + + validate_config(self._module.argument_spec, {'config': updated_config_list}) + + return updated_config_list + + def _get_generated_config(self, commands, have, state): + """Get generated config""" + + new_config = remove_empties_from_list(get_new_config(commands, have, TEST_KEYS_formatted_diff)) + for conf in new_config: + name = conf.get('name') + if name == 'global': + maps = conf.get('map') + if maps: + # To handle check_mode merged case when sonic_roles is set + if maps.get('map_remote_groups_to_sonic_roles'): + match_conf = next((item for item in commands if item['name'] == name), None) + if match_conf: + match_maps = match_conf.get('map') + if match_maps: + for group in maps.get('map_remote_groups_to_sonic_roles', {}): + remote_group = group.get('remote_group') + match_groups = match_maps.get('map_remote_groups_to_sonic_roles', {}) + match_group = next((item for item in match_groups if item['remote_group'] == remote_group), None) + if match_group: + if state != 'deleted': + group['sonic_roles'] = match_group.get('sonic_roles') + + return new_config diff --git a/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py b/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py index cbdbff2f8..88a1c90d3 100644 --- a/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py +++ b/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py @@ -308,11 +308,12 @@ def get_delete_specific_lldp_global_param_requests(self, command, config): if 'enable' in command: url = self.lldp_global_config_path['enable'] + payload = {} if command['enable'] is False: - payload = {'openconfig-lldp:enabled': True} - requests.append({'path': url, 'method': PATCH, 'data': payload}) + payload = {'openconfig-lldp:enabled': True} elif command['enable'] is True: payload = {'openconfig-lldp:enabled': False} + if payload: requests.append({'path': url, 'method': PATCH, 'data': payload}) if 'mode' in command: url = self.lldp_global_config_path['mode'] diff --git a/plugins/module_utils/network/sonic/config/mgmt_servers/mgmt_servers.py b/plugins/module_utils/network/sonic/config/mgmt_servers/mgmt_servers.py new file mode 100644 index 000000000..9bc9f950d --- /dev/null +++ b/plugins/module_utils/network/sonic/config/mgmt_servers/mgmt_servers.py @@ -0,0 +1,531 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_mgmt_servers class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + get_diff, + remove_empties, + update_states +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + edit_config, + to_request +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + get_new_config, + get_formatted_config_diff +) + +SYS_PATH = '/data/openconfig-system:system' +PATCH = 'patch' +DELETE = 'delete' + + +def __derive_rest_delete_op(key_set, command, exist_conf): + new_conf = exist_conf + api_timeout = command.get('api_timeout') + client_auth = command.get('client_auth') + log_level = command.get('log_level') + port = command.get('port') + read_timeout = command.get('read_timeout') + req_limit = command.get('req_limit') + security_profile = command.get('security_profile') + shutdown = command.get('shutdown') + vrf = command.get('vrf') + cfg_api_timeout = new_conf.get('api_timeout') + cfg_client_auth = new_conf.get('client_auth') + cfg_log_level = new_conf.get('log_level') + cfg_port = new_conf.get('port') + cfg_read_timeout = new_conf.get('read_timeout') + cfg_req_limit = new_conf.get('req_limit') + cfg_security_profile = new_conf.get('security_profile') + cfg_shutdown = new_conf.get('shutdown') + cfg_vrf = new_conf.get('vrf') + + if api_timeout is not None and api_timeout == cfg_api_timeout and api_timeout != 900: + new_conf['api_timeout'] = 900 + if client_auth and client_auth == cfg_client_auth and client_auth != 'password,jwt': + new_conf['client_auth'] = 'password,jwt' + if log_level is not None and log_level == cfg_log_level and log_level != 0: + new_conf['log_level'] = 0 + if port is not None and port == cfg_port and port != 443: + new_conf['port'] = 443 + if read_timeout is not None and read_timeout == cfg_read_timeout and read_timeout != 15: + new_conf['read_timeout'] = 15 + if req_limit is not None and req_limit == cfg_req_limit: + new_conf.pop('req_limit') + if security_profile and security_profile == cfg_security_profile: + new_conf.pop('security_profile') + if shutdown is not None and shutdown == cfg_shutdown: + new_conf.pop('shutdown') + if vrf and vrf == cfg_vrf: + new_conf.pop('vrf') + return True, new_conf + + +def __derive_telemetry_delete_op(key_set, command, exist_conf): + new_conf = exist_conf + api_timeout = command.get('api_timeout') + client_auth = command.get('client_auth') + jwt_refresh = command.get('jwt_refresh') + jwt_valid = command.get('jwt_valid') + log_level = command.get('log_level') + port = command.get('port') + security_profile = command.get('security_profile') + vrf = command.get('vrf') + cfg_api_timeout = new_conf.get('api_timeout') + cfg_client_auth = new_conf.get('client_auth') + cfg_jwt_refresh = new_conf.get('jwt_refresh') + cfg_jwt_valid = new_conf.get('jwt_valid') + cfg_log_level = new_conf.get('log_level') + cfg_port = new_conf.get('port') + cfg_security_profile = new_conf.get('security_profile') + cfg_vrf = new_conf.get('vrf') + + if api_timeout is not None and api_timeout == cfg_api_timeout and api_timeout != 0: + new_conf['api_timeout'] = 0 + if client_auth and client_auth == cfg_client_auth and client_auth != 'password,jwt': + new_conf['client_auth'] = 'password,jwt' + if jwt_refresh is not None and jwt_refresh == cfg_jwt_refresh and jwt_refresh != 900: + new_conf['jwt_refresh'] = 900 + if jwt_valid is not None and jwt_valid == cfg_jwt_valid and jwt_valid != 3600: + new_conf['jwt_valid'] = 3600 + if log_level is not None and log_level == cfg_log_level and log_level != 0: + new_conf['log_level'] = 0 + if port is not None and port == cfg_port and port != 8080: + new_conf['port'] = 8080 + if security_profile and security_profile == cfg_security_profile: + new_conf.pop('security_profile') + if vrf and vrf == cfg_vrf: + new_conf.pop('vrf') + return True, new_conf + + +TEST_KEYS_generate_config = [ + {'rest': {'__delete_op': __derive_rest_delete_op}}, + {'telemetry': {'__delete_op': __derive_telemetry_delete_op}} +] + + +class Mgmt_servers(ConfigBase): + """ + The sonic_mgmt_servers class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'mgmt_servers', + ] + + def __init__(self, module): + super(Mgmt_servers, self).__init__(module) + + def get_mgmt_servers_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + mgmt_servers_facts = facts['ansible_network_resources'].get('mgmt_servers') + if not mgmt_servers_facts: + return {} + return mgmt_servers_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = [] + commands = [] + + existing_mgmt_servers_facts = self.get_mgmt_servers_facts() + commands, requests = self.set_config(existing_mgmt_servers_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + result['commands'] = commands + + changed_mgmt_servers_facts = self.get_mgmt_servers_facts() + + result['before'] = existing_mgmt_servers_facts + if result['changed']: + result['after'] = changed_mgmt_servers_facts + + new_config = changed_mgmt_servers_facts + old_config = existing_mgmt_servers_facts + if self._module.check_mode: + result.pop('after', None) + new_config = get_new_config(commands, existing_mgmt_servers_facts, TEST_KEYS_generate_config) + result['after(generated)'] = new_config + if self._module._diff: + result['diff'] = get_formatted_config_diff(old_config, + new_config, + self._module._verbosity) + result['warnings'] = warnings + return result + + def set_config(self, existing_mgmt_servers_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = remove_empties(self._module.params['config']) + have = existing_mgmt_servers_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + requests = [] + state = self._module.params['state'] + diff = get_diff(want, have) + + if state == 'merged': + commands, requests = self._state_merged(diff) + elif state == 'replaced': + commands, requests = self._state_replaced(want, have, diff) + elif state == 'overridden': + commands, requests = self._state_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + return commands, requests + + def _state_merged(self, diff): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = diff + requests = self.get_modify_mgmt_servers_request(commands) + + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_replaced(self, want, have, diff): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + mod_commands = [] + replaced_config, requests = self.get_replaced_config(want, have) + + if replaced_config: + commands.extend(update_states(replaced_config, 'deleted')) + mod_commands = want + else: + mod_commands = diff + + if mod_commands: + mod_request = self.get_modify_mgmt_servers_request(mod_commands) + + if mod_request: + requests.append(mod_request) + commands.extend(update_states(mod_commands, 'replaced')) + + return commands, requests + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + requests = [] + + new_have = deepcopy(have) + if new_have and new_have != want: + is_delete_all = True + self.remove_default_entries(new_have) + del_requests = self.get_delete_mgmt_servers_requests(new_have, new_have, is_delete_all) + requests.extend(del_requests) + commands.extend(update_states(new_have, 'deleted')) + new_have = [] + + if not new_have and want: + mod_commands = want + mod_request = self.get_modify_mgmt_servers_request(mod_commands) + + if mod_request: + requests.append(mod_request) + commands.extend(update_states(mod_commands, 'overridden')) + + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + is_delete_all = False + + if not want: + commands = deepcopy(have) + is_delete_all = True + else: + commands = deepcopy(want) + + self.remove_default_entries(commands) + requests = self.get_delete_mgmt_servers_requests(commands, have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') + else: + commands = [] + + return commands, requests + + def get_modify_mgmt_servers_request(self, commands): + request = None + + if commands: + sys_dict = {} + rest = deepcopy(commands.get('rest')) + telemetry = commands.get('telemetry') + + if rest: + # Change from Ansible naming to OC naming + if rest.get('shutdown') is not None: + rest['openconfig-system-mgmt-servers:disable'] = rest.get('shutdown') + rest.pop('shutdown') + sys_dict['rest-server'] = {'config': rest} + if telemetry: + sys_dict['telemetry-server'] = {'config': telemetry} + if sys_dict: + payload = {'openconfig-system:system': sys_dict} + request = {'path': SYS_PATH, 'method': PATCH, 'data': payload} + + return request + + def get_delete_mgmt_servers_requests(self, commands, have, is_delete_all): + requests = [] + + if not commands or not have: + return requests + if is_delete_all: + requests.append(self.get_delete_request('rest-server', None)) + requests.append(self.get_delete_request('telemetry-server', None)) + return requests + + config_dict = {} + # REST server deletion handling + rest = commands.get('rest') + if rest: + api_timeout = rest.get('api_timeout') + client_auth = rest.get('client_auth') + log_level = rest.get('log_level') + port = rest.get('port') + read_timeout = rest.get('read_timeout') + req_limit = rest.get('req_limit') + security_profile = rest.get('security_profile') + shutdown = rest.get('shutdown') + vrf = rest.get('vrf') + + cfg_rest = have.get('rest') + if cfg_rest: + rest_dict = {} + cfg_api_timeout = cfg_rest.get('api_timeout') + cfg_client_auth = cfg_rest.get('client_auth') + cfg_log_level = cfg_rest.get('log_level') + cfg_port = cfg_rest.get('port') + cfg_read_timeout = cfg_rest.get('read_timeout') + cfg_req_limit = cfg_rest.get('req_limit') + cfg_security_profile = cfg_rest.get('security_profile') + cfg_shutdown = cfg_rest.get('shutdown') + cfg_vrf = cfg_rest.get('vrf') + + if api_timeout is not None and api_timeout == cfg_api_timeout: + requests.append(self.get_delete_request('rest-server', 'api_timeout')) + rest_dict['api_timeout'] = api_timeout + if client_auth and client_auth == cfg_client_auth: + requests.append(self.get_delete_request('rest-server', 'client_auth')) + rest_dict['client_auth'] = client_auth + if log_level is not None and log_level == cfg_log_level: + requests.append(self.get_delete_request('rest-server', 'log_level')) + rest_dict['log_level'] = log_level + if port is not None and port == cfg_port: + requests.append(self.get_delete_request('rest-server', 'port')) + rest_dict['port'] = port + if read_timeout is not None and read_timeout == cfg_read_timeout: + requests.append(self.get_delete_request('rest-server', 'read_timeout')) + rest_dict['read_timeout'] = read_timeout + if req_limit is not None and req_limit == cfg_req_limit: + requests.append(self.get_delete_request('rest-server', 'req_limit')) + rest_dict['req_limit'] = req_limit + if security_profile and security_profile == cfg_security_profile: + requests.append(self.get_delete_request('rest-server', 'security_profile')) + rest_dict['security_profile'] = security_profile + if shutdown is not None and shutdown == cfg_shutdown: + requests.append(self.get_delete_request('rest-server', 'openconfig-system-mgmt-servers:disable')) + rest_dict['shutdown'] = shutdown + if vrf and vrf == cfg_vrf: + requests.append(self.get_delete_request('rest-server', 'vrf')) + rest_dict['vrf'] = vrf + if rest_dict: + config_dict['rest'] = rest_dict + + # Telemetry server deletion handling + telemetry = commands.get('telemetry') + if telemetry: + api_timeout = telemetry.get('api_timeout') + client_auth = telemetry.get('client_auth') + jwt_refresh = telemetry.get('jwt_refresh') + jwt_valid = telemetry.get('jwt_valid') + log_level = telemetry.get('log_level') + port = telemetry.get('port') + security_profile = telemetry.get('security_profile') + vrf = telemetry.get('vrf') + + cfg_telemetry = have.get('telemetry') + if cfg_telemetry: + telemetry_dict = {} + cfg_api_timeout = cfg_telemetry.get('api_timeout') + cfg_client_auth = cfg_telemetry.get('client_auth') + cfg_jwt_refresh = cfg_telemetry.get('jwt_refresh') + cfg_jwt_valid = cfg_telemetry.get('jwt_valid') + cfg_log_level = cfg_telemetry.get('log_level') + cfg_port = cfg_telemetry.get('port') + cfg_security_profile = cfg_telemetry.get('security_profile') + cfg_vrf = cfg_telemetry.get('vrf') + + if api_timeout is not None and api_timeout == cfg_api_timeout: + requests.append(self.get_delete_request('telemetry-server', 'api_timeout')) + telemetry_dict['api_timeout'] = api_timeout + if client_auth and client_auth == cfg_client_auth: + requests.append(self.get_delete_request('telemetry-server', 'client_auth')) + telemetry_dict['client_auth'] = client_auth + if jwt_refresh is not None and jwt_refresh == cfg_jwt_refresh: + requests.append(self.get_delete_request('telemetry-server', 'jwt_refresh')) + telemetry_dict['jwt_refresh'] = jwt_refresh + if jwt_valid is not None and jwt_valid == cfg_jwt_valid: + requests.append(self.get_delete_request('telemetry-server', 'jwt_valid')) + telemetry_dict['jwt_valid'] = jwt_valid + if log_level is not None and log_level == cfg_log_level: + requests.append(self.get_delete_request('telemetry-server', 'log_level')) + telemetry_dict['log_level'] = log_level + if port is not None and port == cfg_port: + requests.append(self.get_delete_request('telemetry-server', 'port')) + telemetry_dict['port'] = port + if security_profile and security_profile == cfg_security_profile: + requests.append(self.get_delete_request('telemetry-server', 'security_profile')) + telemetry_dict['security_profile'] = security_profile + if vrf and vrf == cfg_vrf: + requests.append(self.get_delete_request('telemetry-server', 'vrf')) + telemetry_dict['security_profile'] = vrf + if telemetry: + config_dict['telemetry'] = telemetry + + commands = config_dict + return requests + + def get_delete_request(self, server, attr): + url = '%s/%s' % (SYS_PATH, server) + + if attr: + url += '/config/%s' % (attr) + request = {'path': url, 'method': DELETE} + return request + + def remove_default_entries(self, data): + rest = data.get('rest') + telemetry = data.get('telemetry') + + if rest: + if rest.get('api_timeout') == 900: + data['rest'].pop('api_timeout') + if rest.get('client_auth') == 'password,jwt': + data['rest'].pop('client_auth') + if rest.get('log_level') == 0: + data['rest'].pop('log_level') + if rest.get('port') == 443: + data['rest'].pop('port') + if rest.get('read_timeout') == 15: + data['rest'].pop('read_timeout') + if not rest: + data.pop('rest') + if telemetry: + if telemetry.get('api_timeout') == 0: + data['telemetry'].pop('api_timeout') + if telemetry.get('client_auth') == 'password,jwt': + data['telemetry'].pop('client_auth') + if telemetry.get('jwt_refresh') == 900: + data['telemetry'].pop('jwt_refresh') + if telemetry.get('jwt_valid') == 3600: + data['telemetry'].pop('jwt_valid') + if telemetry.get('log_level') == 0: + data['telemetry'].pop('log_level') + if telemetry.get('port') == 8080: + data['telemetry'].pop('port') + if not telemetry: + data.pop('telemetry') + + def get_replaced_config(self, want, have): + config_dict = {} + requests = [] + rest = want.get('rest') + telemetry = want.get('telemetry') + cfg_rest = have.get('rest') + cfg_telemetry = have.get('telemetry') + + if rest and cfg_rest and rest != cfg_rest: + config_dict['rest'] = cfg_rest + requests.append(self.get_delete_request('rest-server', None)) + if telemetry and cfg_telemetry and telemetry != cfg_telemetry: + config_dict['telemetry'] = cfg_telemetry + requests.append(self.get_delete_request('telemetry-server', None)) + + return config_dict, requests diff --git a/plugins/module_utils/network/sonic/config/ospf_area/ospf_area.py b/plugins/module_utils/network/sonic/config/ospf_area/ospf_area.py new file mode 100644 index 000000000..16ba5af2b --- /dev/null +++ b/plugins/module_utils/network/sonic/config/ospf_area/ospf_area.py @@ -0,0 +1,1153 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_ospf_area class +It is in this file where the current configuration (as list) +is compared to the provided configuration (as list) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + validate_config, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils \ + import ( + get_diff, + update_states, + to_request, + edit_config, + remove_empties + ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + __DELETE_OP_DEFAULT, + __DELETE_SAME_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF, + get_new_config, + get_formatted_config_diff +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_none +) + + +def list_generate_deleted_config_helper(key_set, command, existing_conf, delete_handler): + '''helps with delete state generate config for this module. takes config that are list structures and + if there is matching entry in commands, calls passed in handler on the item. Does some preprocessing, if + commands is empty list empty out current config.''' + if command == []: + # command being empty list means clear list + return [] + if len(existing_conf) == 0: + # early return there's nothing to delete + return existing_conf + if key_set: + command_dict = {tuple(c[field] for field in key_set): c for c in command} + existing_dict = {tuple(c[field] for field in key_set): c for c in existing_conf} + else: + command_dict = {c: c for c in command} + existing_dict = {c: c for c in existing_conf} + + new_conf = [] + # for every existing item, either deleting or not + # keys only in command and not existing do not affect anything + for e_key, e_data in existing_dict.items(): + if e_key in command_dict: + # existing has a matching command for deleting, process it + nu, new_item = delete_handler(key_set, command_dict[e_key], e_data) + if new_item: + # filter out if whole item was deleted. only keep items with something leftover + new_conf.append(new_item) + else: + # existing has no matching command for deleting, can't be changed so keep it + new_conf.append(e_data) + return new_conf + + +def derive_delete_area(key_set, command, exist_conf): + + if len(command) == 2: + # implementation is specifying just area id keys means delete the area + return True, {} + + # default delete seems to be unable to handle case where existing config has many keys, and command + # only specifies a list to clear by providing an empty list, and no other keys. Not all existing keys need + # to be deleted, but default thinks existing should be cleared + + new_conf = deepcopy(exist_conf) + command_data_keys = [key for key in command.keys() if key not in ["ranges", "networks", "stub", "virtual_links", "vrf_name", "area_id"]] + exist_data_keys = [key for key in exist_conf.keys() if key not in ["ranges", "networks", "stub", "virtual_links", "vrf_name", "area_id"]] + both = set(command_data_keys).intersection(exist_data_keys) + if both: + for k in both: + if command[k] == exist_conf[k]: + del new_conf[k] + + if "networks" in command and "networks" in exist_conf: + # new_conf's networks will be things in existing that aren't in command + if command["networks"] == []: + new_conf["networks"] = [] + else: + new_conf["networks"] = list(set(new_conf["networks"]) - set(command["networks"])) + if "ranges" in command and "ranges" in exist_conf: + new_conf["ranges"] = list_generate_deleted_config_helper({"prefix"}, command["ranges"], exist_conf["ranges"], derive_delete_key) + if "stub" in command and "stub" in exist_conf: + nu, new_conf["stub"] = __DELETE_OP_DEFAULT({}, command["stub"], exist_conf["stub"]) + if "virtual_links" in command and "virtual_links" in exist_conf: + new_conf["virtual_links"] = list_generate_deleted_config_helper( + {"router_id"}, command["virtual_links"], + exist_conf["virtual_links"], + derive_delete_vlink + ) + new_conf = remove_empties(new_conf) + if not new_conf or len(new_conf) == 2: + # area after deleting everything specified is empty or just the keys, disregard it + return True, {} + else: + return True, new_conf + + +def derive_delete_vlink(key_set, command, exist_conf): + + if len(command) == 1: + # only virtual link id specified, delete the virtual link + return True, {} + + # have to try clearing all subsections first and then can know if it is ok to delete virtual link + command_data_keys = [key for key in command.keys() if key not in ["router_id", "authentication", "message_digest_list"]] + exist_data_keys = [key for key in exist_conf.keys() if key not in ["router_id", "authentication", "message_digest_list"]] + both = set(command_data_keys).intersection(exist_data_keys) + if both: + for k in both: + if command[k] == exist_conf[k]: + del exist_conf[k] + if "authentication" in command and "authentication" in exist_conf: + nu, new_auth = __DELETE_SAME_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF( + {"key_id"}, + command["authentication"] if command["authentication"] else exist_conf["authentication"], + exist_conf["authentication"] + ) + exist_conf["authentication"] = new_auth + + if "message_digest_list" in command and "message_digest_list" in exist_conf: + if command["message_digest_list"] == []: + exist_conf["message_digest_list"] = [] + else: + md_keys_after = [] + mdk_c_keys = {mdk["key_id"]: mdk for mdk in command.get("message_digest_list", [])} + for md_key in exist_conf["message_digest_list"]: + md_key_c = mdk_c_keys[md_key["key_id"]] + + if md_key_c: + if len(md_key_c) == 1: + continue + nu, new_md_key = __DELETE_SAME_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF({"key_id"}, md_key_c, md_key) + if new_md_key: + md_keys_after.append(new_md_key) + else: + md_keys_after.append(md_key) + exist_conf["message_digest_list"] = md_keys_after + # have taken care of subsections, now for the virtual link + exist_conf = remove_empties(exist_conf) + if exist_conf.keys() == key_set: + return True, {} + else: + return True, exist_conf + + +def derive_delete_key(key_set, command, exist_conf): + if command.keys() == key_set: + return True, {} + else: + return __DELETE_SAME_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF(key_set, command, exist_conf) + + +TEST_KEYS_generate_config = [ + {"config": {"area_id": "", "vrf_name": "", "__delete_op": derive_delete_area}}, + {"ranges": {"prefix": "", "__delete_op": derive_delete_key}}, + {"virtual_links": {"router_id": "", "__delete_op": derive_delete_vlink}}, + {"message_digest_list": {"key_id": "", "__delete_op": derive_delete_key}} +] + + +class Ospf_area(ConfigBase): + """ + The sonic_ospf_area class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ospf_area', + ] + + TEST_KEYS = [ + {"config": {"area_id": "", "vrf_name": ""}}, + {"ranges": {"prefix": ""}}, + {"virtual_links": {"router_id": ""}}, + {"message_digest_list": {"key_id": ""}} + ] + + ospf_uri = "data/openconfig-network-instance:network-instances/network-instance={vrf}/protocols/protocol=OSPF,ospfv2/ospfv2" + # URI to ospf settings for one network_instance + ospf_area_uri = ospf_uri + "/areas/area={area_id}" + # URI to ospf area settings for one network_instance + ospf_propagation_uri = ospf_uri + "/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy={area_id}" + # URI to ospf inter-area-propagation-policies settings for one network_instance + ospf_key_extn = "openconfig-ospfv2-ext:" + auth_type_conversion = {"message_digest": "MD5HMAC", "text": "TEXT", "none": "NONE"} + + def __init__(self, module): + super(Ospf_area, self).__init__(module) + + def get_ospf_area_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A list + :returns: The current configuration as a list of areas' config + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + ospf_area_facts = facts['ansible_network_resources'].get('ospf_area') + if not ospf_area_facts: + return [] + return ospf_area_facts["config"] + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = [] + commands = [] + existing_ospf_area_facts = self.get_ospf_area_facts() + commands, requests = self.set_config(existing_ospf_area_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.errno) + result['changed'] = True + result['commands'] = commands + + result['before'] = existing_ospf_area_facts + new_config = deepcopy(existing_ospf_area_facts) + # just used for diff mode, setting it to a default value that would show no differences. If there are changes then set to changed value + + if self._module.check_mode: + new_config = get_new_config(commands, existing_ospf_area_facts, + TEST_KEYS_generate_config) + result['after(generated)'] = new_config + elif result['changed']: + new_config = self.get_ospf_area_facts() + result['after'] = new_config + if self._module._diff: + new_config.sort(key=lambda x: (x['area_id'], x['vrf_name'])) + existing_ospf_area_facts.sort(key=lambda x: (x['area_id'], x['vrf_name'])) + result['config_diff'] = get_formatted_config_diff(existing_ospf_area_facts, + new_config, + self._module._verbosity) + + result['warnings'] = warnings + return result + + def set_config(self, existing_ospf_area_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_ospf_area_facts + + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a list + :param have: the current configuration as a list + :rtype: A tuple + :returns: the commands necessary to migrate the current configuration + to the desired configuration, and REST requests that do it + """ + commands = [] + requests = [] + state = self._module.params['state'] + want = self.validate_normalize_config(want, have, state) + if state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + elif state == 'overridden': + commands, requests = self._state_overridden(want, have) + else: + commands, requests = self._state_replaced(want, have) + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A tuple of lists + :returns: A list of the commands and state needed to merge the user-specified + new and modified configuration commands into the current + configuration, and a list of the corresponding requests that + need to be sent to the device to make the specified changes + """ + commands = [] + requests = [] + if want is None: + return commands, requests + + diff = get_diff(want, have, test_keys=self.TEST_KEYS) + diff = self.post_process_diff(want, diff) + requests = self.build_areas_merge_requests(diff) + commands = diff + if commands and len(requests) > 0: + commands = update_states(commands, "merged") + else: + commands = [] + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A tuple of lists + :returns: A list of the commands and state needed to delete the user-specified + configuration commands from the current + configuration, and a list of the corresponding requests that + need to be sent to the device to make the specified changes + """ + commands = [] + requests = [] + if not have: + return commands, requests + if not want: + # empty or None assume delete everything + commands = have + requests = self.build_areas_delete_requests(commands, have, delete_everything=True) + else: + commands = self.get_delete_and_clears_recursive( + want, + have, + next((k["config"] for k in self.TEST_KEYS if "config" in k), {}), + test_keys=self.TEST_KEYS + ) + commands = self.post_process_diff(want, commands, merged_mode=False) + # commands is things in want that are in have and are not different aka same + requests = self.build_areas_delete_requests(commands, have) + if commands and len(requests) > 0: + commands = update_states(commands, "deleted") + else: + commands = [] + return commands, requests + + def get_delete_and_clears_recursive(self, want, have, key_fields=None, test_keys=None): + ''' get config that should be deleted because they match or wanted config says to clear everything. + assumes want and have are argspec format and start at same level. any lists in config must have its key in test_keys, + "config" if root of config, and the names of fields that create a key for each item must be passed to each item inside list + + :param key_fields: list or dict (only looks at the keys of dict) holding the names of the fields that create an identifying key for configuration item. + If want and have are dictionary, then the fields in want and have that create a key for want or have + If want and have are lists of dictionaries then the fields in want and have that create a key for each list entry + If its lists of strings, then each string is the key''' + if key_fields is None: + key_fields = {} + if test_keys is None: + test_keys = self.TEST_KEYS + # fill in defaults for helper information + + if isinstance(want, dict): + if want.keys() == key_fields.keys(): + # special case if want only has keys, then that means clear object + # returning what to delete + return have + present_fields = set(want.keys()) & set(have.keys()) + # list of all fields inside the config we are looking at + commands = {} + for field_name in present_fields: + if field_name not in key_fields and field_name in want and field_name in have: + # handling deletion checks, only need to if field is in both want and have + if isinstance(want[field_name], list): + key_filter = [k[field_name] for k in test_keys if field_name in k] + # want to find key fields for nested items, first step is finding if it is in test_keys + sub_section = self.get_delete_and_clears_recursive( + want[field_name], + have[field_name], + key_fields=key_filter[0] if key_filter else {}, + test_keys=test_keys + ) + if sub_section: + commands[field_name] = sub_section + elif isinstance(want[field_name], dict): + sub_section = self.get_delete_and_clears_recursive(want[field_name], have[field_name], key_fields={}, test_keys=test_keys) + if sub_section: + commands[field_name] = sub_section + else: + if want[field_name] == have[field_name]: + commands[field_name] = want[field_name] + if commands: + for key_field in key_fields: + commands[key_field] = want[key_field] + return commands + if isinstance(want, list): + if len(want) == 0: + # special case logic of blank lists means clear everything + # returning what to delete + return have + elif isinstance(want[0], str): + # list of primitive types. cannot do logic of finding keyfields but set logic works + # returning what to delete + return list(set(want) & set(have)) + else: + commands = [] + # use keys to find which items we need to figure out what to delete inside + # create dictionary mapping from each item's identifier to item. identifier can be made of multiple fields in item + want_item_keys = {tuple(item[field] for field in key_fields): item for item in want} + have_item_keys = {tuple(item[field] for field in key_fields): item for item in have} + + matched_item_keys = set(want_item_keys.keys()) & set(have_item_keys.keys()) + # list items that are present in both want and have + # only need to look for deletes when an item appears in both + + for item_key in matched_item_keys: + # for each item, get things that should be deleted + # key fields need to be passed to get right formatting back + sub_section = self.get_delete_and_clears_recursive(want_item_keys[item_key], have_item_keys[item_key], key_fields, test_keys) + if sub_section: + commands.append(sub_section) + return commands + + def _state_replaced(self, want, have): + """ The command generator when state is replaced + + :rtype: A tuple of lists + :returns: A list of what commands and state necessary to migrate the current configuration + to the desired configuration, and a list of requests needed to make changes + """ + commands = [] + requests = [] + + replace_commands = [] + add_commands = [] + + area_h_keys = {(area["area_id"], area["vrf_name"]): area for area in have} + # to make it easy, if an area appears in both have and want, just delete the whole area definition from have and then replace with the want + for area_w in want: + area_h = area_h_keys.get((area_w["area_id"], area_w["vrf_name"]), None) + + if area_h is None: + # new area, no previous definition. which means always need to add it + add_commands.append(area_w) + continue + + diff_remove = get_diff([area_h], [area_w], test_keys=self.TEST_KEYS) + diff_add = get_diff([area_w], [area_h], test_keys=self.TEST_KEYS) + if diff_remove and len(diff_remove) > 0: + # there are differences between have and want, so removing the whole area and replace + replace_commands.append(area_h) + add_commands.append(area_w) + elif diff_add and len(diff_add) > 0: + # just new differences to add + add_commands.append(area_w) + + replace_commands = self.post_process_diff(want, replace_commands, merged_mode=False) + add_commands = self.post_process_diff(want, add_commands) + + if not want: + return commands, requests + if replace_commands: + requests.extend(self.build_areas_delete_requests(replace_commands, have, delete_everything=True)) + commands.extend(update_states(replace_commands, "deleted")) + if add_commands: + requests.extend(self.build_areas_merge_requests(add_commands)) + commands.extend(update_states(add_commands, "replaced")) + return commands, requests + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A tuple of lists + :returns: A list of what commands and state necessary to migrate the current configuration + to the desired configuration, and a list of requests needed to make changes + """ + commands = [] + requests = [] + areas_w_keys = {(area["vrf_name"], area["area_id"]): area for area in want} + areas_h_keys = {(area["vrf_name"], area["area_id"]): area for area in have} + + area_keys = set(areas_w_keys.keys()) | set(areas_h_keys.keys()) + + deleted_commands = [] + added_commands = [] + + for area_key in area_keys: + if area_key in areas_h_keys and area_key not in areas_w_keys: + # override: any areas that are in have but not in want are deleted + deleted_commands.append(areas_h_keys[area_key]) + elif area_key not in areas_h_keys and area_key in areas_w_keys: + # override: any areas that are in want but not in have are added + added_commands.append(areas_w_keys[area_key]) + elif area_key in areas_h_keys and area_key in areas_w_keys: + diff_remove = get_diff([areas_h_keys[area_key]], [areas_w_keys[area_key]], test_keys=self.TEST_KEYS) + diff_add = get_diff([areas_w_keys[area_key]], [areas_h_keys[area_key]], test_keys=self.TEST_KEYS) + if diff_remove and len(diff_remove) > 0: + # just deleting and adding the whole areas + deleted_commands.append(areas_h_keys[area_key]) + added_commands.append(areas_w_keys[area_key]) + elif diff_add and len(diff_add) > 0: + added_commands.append(areas_w_keys[area_key]) + + deleted_commands = self.post_process_diff(have, deleted_commands, merged_mode=False) + added_commands = self.post_process_diff(want, added_commands) + + if deleted_commands: + requests.extend(self.build_areas_delete_requests(deleted_commands, have, delete_everything=True)) + commands.extend(update_states(deleted_commands, "deleted")) + if added_commands: + requests.extend(self.build_areas_merge_requests(added_commands)) + commands.extend(update_states(added_commands, "overridden")) + return commands, requests + + def validate_normalize_config(self, config, have, state): + '''validates config and and normalizes format of data. Normalization includes formatting area id, checking setting default cost, + and filling in auth key information. + :returns: config object that has been validated and normalized''' + if not config: + return [] + config = {"config": config} + config = remove_none(config) + # validate_config returns validated user input config. The returned data is based on the + # argspec definition. At each nested level of the argspec for which the user has specified + # one or more attributes, the returned data contains added nulls for any attributes that + # were not specified by the user input. + config = validate_config(self._module.argument_spec, config) + # not really using the none values in this module so getting thrown out. Use empty lists for clear + config = remove_none(config)["config"] + area_h_keys = {(area["area_id"], area["vrf_name"]): area for area in have} + for area in config: + try: + area['area_id'] = self.format_area_name(area['area_id']) + except Exception as exc: + self._module.fail_json(msg=str(exc)) + + if state != "deleted": + # only when trying to merge is it an issue if default cost is being set when not stub or NSSA + # finding out if default_cost can be set depends on have and commands + area_h = area_h_keys.get((area['area_id'], area['vrf_name']), None) + can_set_cost = (area_h is not None and area_h.get("stub", {}).get('enabled', False)) or area.get("stub", {}).get('enabled', False) + if area.get("default_cost") and not can_set_cost: + self._module.fail_json(msg="cannot set default cost for area id {area_id} in ".format(area_id=area['area_id']) + + "vrf {vrf_name} because it is not stub or NSSA area".format(vrf_name=area['vrf_name'])) + + for virtual_link in area.get("virtual_links", []): + if "authentication" in virtual_link: + if state != "deleted": + # key is always going to either be encrypted or not. + # key_encrypted field only really should exist if key exists so fill in encrypted if missing and defaults to false + if "key" in virtual_link["authentication"] and "key_encrypted" not in virtual_link["authentication"]: + virtual_link["authentication"]["key_encrypted"] = False + for md_key in virtual_link.get("message_digest_list", []): + if state != "deleted": + if "key" not in md_key: + # any state that is adding config cannot have a missing key + self._module.fail_json(msg="area id'd {area_id} for vrf ".format(area_id=area['area_id']) + + "{vrf_name} has missing key ".format(vrf_name=area['vrf_name']) + + "for key_id {id} in message_digest_list ".format(id=md_key["key_id"]) + + "section of virtual link {vlink}".format(vlink=virtual_link['router_id'])) + if "key_encrypted" not in md_key: + md_key["key_encrypted"] = False + return config + + def format_area_name(self, area_id): + """area names in playbook can be single numbers or as four octet numbers, switch works with area names as the latter. + make sure things are in octect format by applying formatting where needed""" + if area_id.count(".") < 3: + area_int = int(area_id) + return ".".join([str(area_int >> 24 & 0xff), str(area_int >> 16 & 0xff), str(area_int >> 8 & 0xff), str(area_int & 0xff)]) + return area_id + + def post_process_diff(self, want, diff, merged_mode=True): + '''post process the diff between want and have by keeping any wanted auth key settings together. + :param want: the config that the difference is based off of. the first item in the get_diff call + :param diff: the diff between want and have config in argspec format. assumes diff is a subset of want''' + # whatever values were set for key and key encrypted should be kept together + post_cleaned_diff = [] + area_w_keys = {(area["area_id"], area["vrf_name"]): area for area in want} + + for area_d in diff: + area_w = area_w_keys.get((area_d["area_id"], area_d["vrf_name"]), None) + if merged_mode and len(area_w) == 2: + # commands has an area with no settings to merge, that doesn't show up in facts because it can break other stuff, + # so putting in step to ignore + continue + if not area_w: + continue + + virtual_w_keys = {vl["router_id"]: vl for vl in area_w.get("virtual_links", [])} + for virtual_d in area_d.get("virtual_links", []): + # virtual_d = virtual_d_keys.get(virtual_w["router_id"], None) + virtual_w = virtual_w_keys.get(virtual_d["router_id"], None) + if not virtual_w: + continue + # key always has a key_encrypted setting. They are defined together so they must travel as a pair. Handle violations of this + # requirement presented by the default 'get_diff' + # ie playbook may specify encrypted key A, and device has encrypted key B. get diff will find the keys different, not the key_encrypted + # setting and only the key goes into the diff. The diff is missing the encrypted setting so it needs to grab that from existing settings. + if virtual_d.get("authentication", {}).get("key") and virtual_d.get("authentication", {}).get("key_encrypted") is None \ + and virtual_w.get("authentication", {}).get("key_encrypted") is not None: + # specified a different key that is also encrypted. fix that error in diff + virtual_d["authentication"]["key_encrypted"] = virtual_w["authentication"]["key_encrypted"] + if not virtual_d.get("authentication", {}).get("key") and virtual_d.get("authentication", {}).get("key_encrypted") is not None \ + and virtual_w.get("authentication", {}).get("key"): + # same key but different encryption + # this is likely error situation since single key can't work for both un- and encrypted + # and just make sure two are together for easier debugging + virtual_d["authentication"]["key"] = virtual_w["authentication"]["key"] + + mdk_w_keys = {mdk["key_id"]: mdk for mdk in virtual_w.get("message_digest_list", [])} + for md_key_d in virtual_d.get("message_digest_list", []): + md_key_w = mdk_w_keys.get(md_key_d["key_id"], None) + if not md_key_d: + # message digest key didn't end up with a difference, so nothing to do + continue + if md_key_d.get("key") and md_key_d.get("key_encrypted") is None \ + and md_key_w.get("key_encrypted") is not None: + # specified a different key that is also encrypted. fixes that error + md_key_d["key_encrypted"] = md_key_w["key_encrypted"] + if not md_key_d.get("key") and md_key_d.get("key_encrypted") is not None \ + and md_key_w.get("key"): + # same key but different encryption + # this is likely error situation since single key can't work for both un- and encrypted + # and just make sure two are together for easier debugging + md_key_d["key"] = md_key_w["key"] + if merged_mode: + area_d = remove_empties(area_d) + post_cleaned_diff.append(area_d) + return post_cleaned_diff + + def build_areas_merge_requests(self, want): + '''takes a list of areas and builds up all requests to patch in wanted changes''' + requests = [] + formatted_bodies = {} + vlink_requests = [] + # tracking all formatted lists that will go into the final requests for each vrf + + # list of areas is not organized by VRF and REST requests are being consolidated into a call on each VRF, so need to sort the area list. + # Also the organization is different between REST and argspec, argspec has config that goes into ospf areas and + # ospf global inter-area-propagation-policies of the REST format. + # Since REST has two distinct subsections, these are formatted separately and then conbimned into one REST request + for area in want: + # if an area is passed in, assuming want to create it no matter what settings (or just area id) passed in, so area has to exist + formatted_area = self.format_area_options_to_rest(area) + formatted_area_policy = self.format_area_policy_to_rest(area) + if "virtual_links" in area: + # merging vlinks is done as a separate request from rest of area settings. + # Area settings go to the vrf configuration endpoint, vlinks go to the virtual link endpoint. + # This allows vlinks with just the router id with no other settings specified to be created. + # These requests still depend on area being created first, so on the safe side, getting area requests first before handlign virtual links + vlink_requests.extend(self.build_area_vlink_merge_requests( + self.ospf_area_uri.format(vrf=area["vrf_name"], area_id=area["area_id"]), + area["virtual_links"] + )) + # consolidate by Vrf + if (formatted_area or formatted_area_policy) and area["vrf_name"] not in formatted_bodies: + formatted_bodies[area["vrf_name"]] = {"areas": [], "propagation": []} + if formatted_area: + formatted_bodies[area["vrf_name"]]["areas"].append(formatted_area) + if formatted_area_policy: + formatted_bodies[area["vrf_name"]]["propagation"].append(formatted_area_policy) + + # build requests on vrf + for vrf in formatted_bodies: + vrf_request_body = {} + if formatted_bodies[vrf]["areas"]: + vrf_request_body["areas"] = {"area": formatted_bodies[vrf]["areas"]} + if formatted_bodies[vrf]["propagation"]: + vrf_request_body["global"] = {"inter-area-propagation-policies": { + self.ospf_key_extn + "inter-area-policy": formatted_bodies[vrf]["propagation"] + }} + if vrf_request_body: + requests.append({"path": self.ospf_uri.format(vrf=vrf), "method": "PATCH", "data": {"openconfig-network-instance:ospfv2": vrf_request_body}}) + requests.extend(vlink_requests) + return requests + + def build_area_vlink_merge_requests(self, path_root, want_vlinks): + '''build requests for an area's virtual links + :param path_root: the URI for the specific area the vlinks are being added in + :param want_vlinks: the list of virtual links to be added''' + requests = [] + formatted_vlinks = self.format_vlinks_to_rest(want_vlinks) + if formatted_vlinks: + # endpoint is an area's virtual links. + requests.append({"path": path_root + "/virtual-links", + "method": "PATCH", "data": {"openconfig-network-instance:virtual-links": {"virtual-link": formatted_vlinks}}}) + return requests + + def format_area_options_to_rest(self, want): + '''takes a single area config and formats it for the body of an REST patch request. + Only formats the part that fall under the area object in REST format, except for vlinks which are added separately''' + formatted_area = {} + + formatted_config = self.format_area_config_to_rest(want) + if formatted_config: + formatted_area["config"] = formatted_config + + formatted_stub_config = {} + if "stub" in want: + # need enabled flag in stub so can make a stub without setting other settings + if "enabled" in want["stub"]: + formatted_stub_config["enable"] = want["stub"]["enabled"] + if "no_summary" in want["stub"]: + formatted_stub_config["no-summary"] = want["stub"]["no_summary"] + if "default_cost" in want: + formatted_stub_config["default-cost"] = want["default_cost"] + + # can't set default_cost on an area that isn't stub or NSAA. + + if formatted_stub_config: + formatted_area[self.ospf_key_extn + "stub"] = {"config": formatted_stub_config} + + if "networks" in want: + formatted_networks = self.format_area_networks_to_rest(want["networks"]) + if formatted_networks: + formatted_area[self.ospf_key_extn + "networks"] = {"network": formatted_networks} + + # skip adding formatted virtual links into area settings because that will be added separately + if formatted_area: + # either settings found to be merged into the areas part of ospf settings or there's settings in the inter-area-policies section + formatted_area["identifier"] = want["area_id"] + return formatted_area + + def format_area_config_to_rest(self, want): + '''takes config wanting to be merged and formats the body of the area object's config in REST format''' + formatted_config = {} + if "authentication_type" in want: + formatted_config[self.ospf_key_extn + "authentication-type"] = self.auth_type_conversion[want["authentication_type"]] + if "shortcut" in want: + formatted_config[self.ospf_key_extn + "shortcut"] = want["shortcut"].upper() + formatted_config["identifier"] = want["area_id"] + return formatted_config + + def format_area_networks_to_rest(self, want): + formatted_networks = [] + for network_prefix in want: + formatted_network = {"address-prefix": network_prefix, "config": {"address-prefix": network_prefix}} + formatted_networks.append(formatted_network) + return formatted_networks + + def format_vlinks_to_rest(self, want): + '''takes a list of virtual link settings and formats it for REST requests''' + formatted_vlinks = [] + for vlink_settings in want: + formatted_vlink = {} + formatted_vlink_config = {} + if "enabled" in vlink_settings: + formatted_vlink_config[self.ospf_key_extn + "enable"] = vlink_settings["enabled"] + if "dead_interval" in vlink_settings: + formatted_vlink_config[self.ospf_key_extn + "dead-interval"] = vlink_settings["dead_interval"] + if "hello_interval" in vlink_settings: + formatted_vlink_config[self.ospf_key_extn + "hello-interval"] = vlink_settings["hello_interval"] + if "retransmit_interval" in vlink_settings: + formatted_vlink_config[self.ospf_key_extn + "retransmission-interval"] = vlink_settings["retransmit_interval"] + if "transmit_delay" in vlink_settings: + formatted_vlink_config[self.ospf_key_extn + "transmit-delay"] = vlink_settings["transmit_delay"] + if "authentication" in vlink_settings: + if "auth_type" in vlink_settings["authentication"]: + formatted_vlink_config[self.ospf_key_extn + "authentication-type"] = \ + self.auth_type_conversion[vlink_settings["authentication"]["auth_type"]] + if "key" in vlink_settings["authentication"]: + formatted_vlink_config[self.ospf_key_extn + "authentication-key"] = vlink_settings["authentication"]["key"] + if "key_encrypted" in vlink_settings["authentication"]: + formatted_vlink_config[self.ospf_key_extn + "authentication-key-encrypted"] = vlink_settings["authentication"]["key_encrypted"] + if "message_digest_list" in vlink_settings: + formatted_vlink[self.ospf_key_extn + "md-authentications"] = {"md-authentication": + self.format_md_keys_to_rest(vlink_settings["message_digest_list"])} + formatted_vlink_config["remote-router-id"] = vlink_settings["router_id"] + formatted_vlink["config"] = formatted_vlink_config + formatted_vlink["remote-router-id"] = vlink_settings["router_id"] + formatted_vlinks.append(formatted_vlink) + return formatted_vlinks + + def format_md_keys_to_rest(self, want): + '''takes the list of message digest keys in argspec format and formats it for REST body''' + formatted_keys = [] + for message_key_settings in want: + formatted_key_config = {} + if "key_encrypted" in message_key_settings: + formatted_key_config["authentication-key-encrypted"] = message_key_settings["key_encrypted"] + formatted_key_config["authentication-key-id"] = message_key_settings["key_id"] + if "key" in message_key_settings: + formatted_key_config["authentication-md5-key"] = message_key_settings["key"] + else: + self._module.fail_json(msg="message digest key is required in md_authentications") + formatted_keys.append({"authentication-key-id": message_key_settings["key_id"], "config": formatted_key_config}) + return formatted_keys + + def format_area_policy_to_rest(self, want): + '''formats area's settings that should go into the inter-area-propagation-policies section of ospf''' + formatted_area_policy = {} + if "ranges" in want: + formatted_ranges = self.format_ranges_to_rest(want["ranges"]) + if formatted_ranges: + formatted_area_policy["ranges"] = {"range": formatted_ranges} + if "filter_list_in" in want: + formatted_area_policy["filter-list-in"] = {"config": {"name": want["filter_list_in"]}} + if "filter_list_out" in want: + formatted_area_policy["filter-list-out"] = {"config": {"name": want["filter_list_out"]}} + if formatted_area_policy: + formatted_area_policy["src-area"] = want["area_id"] + return formatted_area_policy + + def format_ranges_to_rest(self, want): + '''format the ranges an adrea advertises into REST body format. Takes a list of ranges''' + formatted_ranges = [] + for range_settings in want: + formatted_range_config = {} + if "advertise" in range_settings: + formatted_range_config["advertise"] = range_settings["advertise"] + if "cost" in range_settings: + formatted_range_config["metric"] = range_settings["cost"] + if "substitute" in range_settings: + # note it is mispelled in REST + formatted_range_config["substitue-prefix"] = range_settings["substitute"] + # can add range even without other settings + formatted_range_config["address-prefix"] = range_settings["prefix"] + formatted_ranges.append({"address-prefix": range_settings["prefix"], "config": formatted_range_config}) + return formatted_ranges + + def build_areas_delete_requests(self, commands, have, delete_everything=False): + '''takes in a list of areas and builds all required 'delete' requests for the specified areas + :param commands: list of areas to make delete requests for. assumed to be a subset of have + :param have: the current config in argspec format, at the top level of definition + :param delete_everything: whether to delete config for all area''' + requests = [] + area_h_keys = {(area["area_id"], area["vrf_name"]): area for area in have} + for area_c in commands: + # want to match so can find out if commands specifies everything within existing area so can de a delete all of area + matched_have = area_h_keys.get((area_c["area_id"], area_c["vrf_name"]), None) + if not matched_have: + # would mean nothing to delete, just ignore. should not hit since commands should be a subset of have + continue + area_delete_requests = self.build_area_delete_requests(area_c, matched_have, delete_everything) + requests.extend(area_delete_requests) + return requests + + def build_area_delete_requests(self, commands, have, delete_everything=False): + '''builds the requests to delete configuration under the areas section of config for a single area. takes a pair of single area commands and have. + returns tuple of whether everything in area was deleted and requests to cause changes''' + requests = [] + # can't go directly to deleting area, need to check for ranges, network, virtual links - + # most of nested and complex settings - and delete those first (and separately from area) + if len(commands) == 2 or delete_everything: + # allow specifying area id to mean clear it + delete_everything = True + commands = have + # while clearing subsections for area, area itself may get removed in certain cases, for example clearing virtual links when it is the only + # config inside area. + # This variable tracks if above has happened. It updates regardless of whether or not module clears all settings for area, but + # is only used in case of clearing all settings and area. This prevents module from making a second unnecessary and + # error-causing request in cases where area is removed early. + area_already_deleted = False + + ranges_all_gone, ranges_delete_requests = self.build_area_delete_ranges_requests( + self.ospf_propagation_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]), + commands.get("ranges", None), + have.get("ranges", []) + ) + requests.extend(ranges_delete_requests) + + networks_all_gone, networks_delete_requests = self.build_area_delete_networks_requests( + self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]), + commands.get("networks", None), + have.get("networks", []) + ) + requests.extend(networks_delete_requests) + + if "authentication_type" in commands: + requests.append({"path": self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]) + + "/config/openconfig-ospfv2-ext:authentication-type", "method": "DELETE"}) + if all(x in ["authentication_type", "networks", "ranges", "area_id", "vrf_name"] for x in commands.keys()) \ + and networks_all_gone and ranges_all_gone: + # if this is the only setting left in area then it causes area delete + area_already_deleted = True + if "shortcut" in commands: + requests.append({"path": self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]) + + "/config/openconfig-ospfv2-ext:shortcut", "method": "DELETE"}) + if all(x in ["shortcut", "networks", "ranges", "area_id", "vrf_name"] for x in commands.keys()) \ + and networks_all_gone and ranges_all_gone: + # if this is the only setting left in area then it causes area delete + area_already_deleted = True + + vlink_all_gone, vlink_delete_requests, vlink_deleted_area = self.build_area_virtual_links_delete_requests( + self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]), + commands.get("virtual_links", None), + have.get("virtual_links", []) + ) + # if no stub or propagation settings, then area_already_deleted value would be whatever vlink_deleted_area is + # if there are either of those settings, then vlink_deleted_area is only guessing if area was deleted based off of vlinks + # and could be incorrect because of the other settings being there. but it is ok to set it because this variable is only + # used when clearing everything so those sections will end up deleting area. + area_already_deleted = area_already_deleted or \ + (all(x not in ["default_cost", "stub", "filter_list_in", "filter_list_out"] for x in commands.keys()) and vlink_deleted_area) + requests.extend(vlink_delete_requests) + + # gathering stub deletions before checking area all gone because + # it is used to check if area can be deleted + stub_all_gone, stub_delete_requests, stub_deleted_area = self.build_area_stub_delete_requests( + self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]), + commands, + have + ) + area_already_deleted = area_already_deleted or (all(x not in ["filter_list_in", "filter_list_out"] for x in commands.keys()) and stub_deleted_area) + requests.extend(stub_delete_requests) + + # This section, for 'propagation endpoint', handles the inter-area propagation policies + # this doesn't use a "propagation_all_gone" flag because the nested subection ranges are dealt with separately above. + # The other settings of filter lists are nested in propagation in REST API but are in root area settings for argspec. + # This means that a simple check that commands and want (assuming this was passed commands that are in and the same value as have) + # are the same length is enough to cover the same behaviors + propagation_delete_requests, propagation_deleted_area = self.build_area_delete_propagation_requests( + commands, + have, + ranges_all_gone, + ranges_delete_requests) + requests.extend(propagation_delete_requests) + area_already_deleted = area_already_deleted or propagation_deleted_area + + if delete_everything or \ + (len(commands) == len(have) and stub_all_gone and vlink_all_gone and ranges_all_gone and networks_all_gone): + # delete all settings for area. + # either only area id was specified or any form of clear data, + # or all settings are named and for the more complex nested subsections of argspec, + # those subsections also match and are cleared (need the extra flag since length only compares the subsections existence not contents) + # there are cases where deleting sub-sections causes area to disappear. need to make sure to delete + # area too so need to check when adding that is needed + if not area_already_deleted: + requests.append({'path': self.ospf_area_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]), 'method': 'DELETE'}) + return requests + return requests + + def build_area_stub_delete_requests(self, request_root, commands, have): + '''builds the requests to delete a single area's stub config. takes a pair of single area commands and have. + returns a tuple of whether everything in stub config was deleted and requests to cause changes''' + requests = [] + relevant_have = {"default_cost": have.get("default_cost", None), + "no_summary": have.get('stub', {}).get("no_summary", None), + "enabled": have.get('stub', {}).get("enabled", None)} + relevant_commands = {"default_cost": commands.get("default_cost", None), + "no_summary": commands.get('stub', {}).get("no_summary", None), + "enabled": commands.get('stub', {}).get("enabled", None)} + relevant_have = remove_empties(relevant_have) + relevant_commands = remove_empties(relevant_commands) + + if "default_cost" in relevant_commands: + requests.append({"path": request_root + + "/openconfig-ospfv2-ext:stub/config/default-cost", "method": "DELETE"}) + if "no_summary" in relevant_commands: + requests.append({"path": request_root + + "/openconfig-ospfv2-ext:stub/config/no-summary", "method": "DELETE"}) + if "enabled" in relevant_commands: + requests.append({"path": request_root + + "/openconfig-ospfv2-ext:stub/config/enable", "method": "DELETE"}) + if len(relevant_have) == len(relevant_commands): + # either clearing everything left or there's nothing + # actually clearing stuff means deleting area + # area having nothing related to stub also ends up in this case. requests will be empty. + return True, requests, len(requests) > 0 + elif len(requests) > 0: + # clearing some of the settings in stub but not all + # commands has to be a subset of have and whatever in it is translated into requests + return False, requests, False + + def build_area_delete_networks_requests(self, request_root, commands, have): + if commands is None: + return len(have) == 0, [] + if len(have) == 0: + return True, [] + if len(commands) == len(have) or len(commands) == 0: + return True, [{"path": request_root + "/openconfig-ospfv2-ext:networks/network", "method": "DELETE"}] + requests = [] + for address_prefix in commands: + network_string = address_prefix.replace("/", "%2F") + requests.append({"path": request_root + "/openconfig-ospfv2-ext:networks/network=" + network_string, "method": "DELETE"}) + return False, requests + + def build_area_virtual_links_delete_requests(self, request_root, commands, have): + '''builds the requests to delete a single area's virtual link config. + takes a pair of an area's virtual link commands and have. + returns a tuple of whether everything in area's virtual link config was deleted and requests to cause changes''' + if commands is None: + return len(have) == 0, [], False + requests = [] + partial_deletes = False + vlink_deleted_area = False + if len(have) == 0: + return True, [], vlink_deleted_area + if len(commands) == 0: + return True, [{"path": request_root + "/virtual-links/virtual-link", "method": "DELETE"}], True + vlink_h_keys = {vlink["router_id"]: vlink for vlink in have} + for vlink_c in commands: + matched_vlink = vlink_h_keys[vlink_c["router_id"]] + if not matched_vlink: + continue + vlink_uri = request_root + "/virtual-links/virtual-link=" + vlink_c["router_id"] + if len(vlink_c) == 1: + # just the remote router id specified so deleting everything inside a + # virtual link. don't need to process individual attribute delete requests + # so delete the vlink and move to next + requests.append({"path": vlink_uri, "method": "DELETE"}) + continue + + # check vlink subsections to see if they were also all deleted or need individual delete requests + # doing this first as this is used to determine if can just do one request to delete this vlink + if "message_digest_list" in vlink_c: + md_all_gone, md_auth_requests = self.build_area_md_auth_delete_requests(vlink_uri, vlink_c["message_digest_list"], + matched_vlink["message_digest_list"]) + else: + # no message digest keys, means subsection does not need any work on it + md_all_gone = True + md_auth_requests = [] + + if len(vlink_c) == len(matched_vlink) and md_all_gone: + # deleting everything inside a virtual link. don't need to process individual attribute delete requests + # so delete the vlink and move to next + requests.append({"path": request_root + "/virtual-links/virtual-link=" + vlink_c["router_id"], "method": "DELETE"}) + continue + partial_deletes = True + # deleting individual attributes of a vlink, set flag for info that st least one interface needs to delete individual attributes + requests.extend(md_auth_requests) + if "enabled" in vlink_c: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:enable", "method": "DELETE"}) + if "dead_interval" in vlink_c: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:dead-interval", "method": "DELETE"}) + if "hello_interval" in vlink_c: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:hello-interval", "method": "DELETE"}) + if "retransmit_interval" in vlink_c: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:retransmission-interval", "method": "DELETE"}) + if "transmit_delay" in vlink_c: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:transmit-delay", "method": "DELETE"}) + if "authentication" in vlink_c: + if "auth_type" in vlink_c["authentication"] or len(vlink_c["authentication"]) == 0: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:authentication-type", "method": "DELETE"}) + if "key" in vlink_c["authentication"] or len(vlink_c["authentication"]) == 0: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:authentication-key", "method": "DELETE"}) + if "key_encrypted" in vlink_c["authentication"] or len(vlink_c["authentication"]) == 0: + requests.append({"path": vlink_uri + "/config/openconfig-ospfv2-ext:authentication-key-encrypted", "method": "DELETE"}) + if len(commands) == len(have) and not partial_deletes: + return True, [{"path": request_root + "/virtual-links/virtual-link", "method": "DELETE"}], True if len(commands) < 2 else False + return False, requests, False + + def build_area_md_auth_delete_requests(self, request_root, commands, have): + '''builds the requests to delete message digest keys for a single area's virtual link config. + takes a pair of an area's virtual link's message digest commands and have + returns a tuple of whether everything in message digest keys was deleted and requests to cause changes''' + requests = [] + if len(have) == 0: + # nothing in message digest keys to delete + return True, [] + elif len(commands) == len(have) or len(commands) == 0: + # commands should only contain keys that are in have + # deleted everything in have, just return one command to delete root + return True, [{"path": request_root + "/openconfig-ospfv2-ext:md-authentications/md-authentication", "method": "DELETE"}] + for md_c in commands: + # For md key deletion, only the specified key_id is used. If a key value + # and/or encrypted state are specified in the user playbook, they are + # ignored. These attributes are deleted from the configuration for the + # specified key_id solely based on the key_id value specified. + requests.append({"path": request_root + "/openconfig-ospfv2-ext:md-authentications/md-authentication=" + str(md_c["key_id"]), "method": "DELETE"}) + return False, requests + + def build_area_delete_propagation_requests(self, commands, have, ranges_all_gone, ranges_delete_requests): + '''builds the requests to delete a single area's propagation config. takes a single area commands and have. + This has a weird edge case. + Ranges are a part of inter-area-policy, but deleting on the specific inter-area-policy for this area in the REST API will not delete ranges as well. + Deleting area depends on deleting all of the ranges, so that is collected and handled in area. + So this section won't be handling ranges but also needs to know if ranges are cleared to check if ok to delete the whole policy. + returns a tuple of if all propagation settings were deleted and requests to cause changes''' + requests = [] + request_root = self.ospf_propagation_uri.format(vrf=commands["vrf_name"], area_id=commands["area_id"]) + area_deleted = False + + # doing filter list removes if necessary + if "filter_list_in" in commands: + requests.append({"path": request_root + "/filter-list-in", "method": "DELETE"}) + if "filter_list_out" in commands: + requests.append({"path": request_root + "/filter-list-out", "method": "DELETE"}) + + relevant_have = {"filter_list_in": have.get("filter_list_in", None), "filter_list_out": have.get("filter_list_out", None), + "ranges": have.get("ranges", None)} + relevant_commands = {"filter_list_in": commands.get("filter_list_in", None), + "filter_list_out": commands.get("filter_list_out", None), "ranges": commands.get("ranges", None)} + relevant_have = remove_empties(relevant_have) + relevant_commands = remove_empties(relevant_commands) + + if len(relevant_commands) != len(relevant_have) or not ranges_all_gone: + # didn't call to clear everything + return requests, area_deleted + + if len(relevant_commands) > 0 and relevant_have.keys() != {"ranges"}: + # deleting everything case, and actually have deleted things + # if just ranges are deleted then don't try to delete on root, deleting ranges will do that + delete_all_list = [{"path": request_root, "method": "DELETE"}] + return delete_all_list, True + # nothing for propagation - that isn't ranges settings - deleted + return [], area_deleted + + def build_area_delete_ranges_requests(self, request_root, commands, have): + '''builds list of requests to delete one area's settings for ranges. takes one area's commands and have list of ranges. + Commands has to be a subset of have. + returns a tuple of whether everything in ranges was deleted and requests to cause changes''' + if commands is None: + return len(have) == 0, [] + requests = [] + partial_deletes = False + if len(have) == 0: + # nothing in ranges to delete + return True, [] + if len(commands) == 0: + return True, [{"path": request_root + "/ranges/range", "method": "DELETE"}] + range_h_keys = {range["prefix"]: range for range in have} + for range_c in commands: + matched_range = range_h_keys[range_c["prefix"]] + if not matched_range: + # should not hit as commands must be a subset of have + continue + + range_string = range_c["prefix"].replace("/", "%2F") + if len(range_c) == 1 or len(range_c) == len(matched_range): + # only the range prefix specified or same number of fields specified means delete the whoe range + requests.append({"path": request_root + "/ranges/range=" + range_string, "method": "DELETE"}) + continue + + partial_deletes = True + if "advertise" in range_c: + requests.append({"path": request_root + "/ranges/range=" + range_string + "/config/advertise", "method": "DELETE"}) + if "cost" in range_c: + requests.append({"path": request_root + "/ranges/range=" + range_string + "/config/metric", "method": "DELETE"}) + if "substitute" in range_c: + # it actually is mispelled as substitue in REST + requests.append({"path": request_root + "/ranges/range=" + range_string + "/config/substitue-prefix", + "method": "DELETE"}) + if len(commands) == len(have) and not partial_deletes: + # deleting all ranges + return True, [{"path": request_root + "/ranges/range", "method": "DELETE"}] + return False, requests diff --git a/plugins/module_utils/network/sonic/config/ospfv2/__init__.py b/plugins/module_utils/network/sonic/config/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/ospfv2/ospfv2.py b/plugins/module_utils/network/sonic/config/ospfv2/ospfv2.py new file mode 100644 index 000000000..e2dcee191 --- /dev/null +++ b/plugins/module_utils/network/sonic/config/ospfv2/ospfv2.py @@ -0,0 +1,766 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_ospfv2 class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + update_states, + get_diff, + remove_matching_defaults, + get_normalize_interface_name, + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + get_new_config, + get_formatted_config_diff, + __DELETE_CONFIG, + __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF +) +from ansible.module_utils.connection import ConnectionError + +PATCH = 'patch' +DELETE = 'delete' +DIRECTION = 'IMPORT' + +OSPF_PATH = ('/data/openconfig-network-instance:network-instances/network-instance={vrf_name}' + '/protocols/protocol=OSPF,ospfv2/ospfv2') + +DEFAULT_ADDRESS = '0.0.0.0' + + +TEST_KEYS = [ + {'config': {'vrf_name': ''}}, + {'helper': {'advertise_router_id': ''}}, + {'passive_interfaces': {'interface': ''}}, + {'non_passive_interfaces': {'interface': ''}}, + {'redistribute': {'protocol': ''}} +] + +TEST_KEYS_overridden_diff = [ + {'config': {'vrf_name': '', '__delete_op': __DELETE_CONFIG}}, + {'helper': {'advertise_router_id': '', '__delete_op': __DELETE_CONFIG}}, + {'passive_interfaces': {'interface': '', '__delete_op': __DELETE_CONFIG}}, + {'non_passive_interfaces': {'interface': '', '__delete_op': __DELETE_CONFIG}}, + {'redistribute': {'protocol': '', '__delete_op': __DELETE_CONFIG}}, + {'graceful_restart': {'helper': '', '__delete_op': __DELETE_CONFIG}} +] + +TEST_KEYS_diff = [ + {'config': {'vrf_name': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'helper': {'advertise_router_id': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'passive_interfaces': {'interface': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'non_passive_interfaces': {'interface': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'redistribute': {'protocol': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'graceful_restart': {'helper': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}} +] + +DEFAULT_ENTRIES = [ + [ + {'name': 'max_metric'}, + {'name': 'external_lsa_all', 'default': 16777215} + ], + [ + {'name': 'max_metric'}, + {'name': 'external_lsa_connected', 'default': 16777215} + ], + [ + {'name': 'max_metric'}, + {'name': 'router_lsa_all', 'default': 16777215} + ], + [ + {'name': 'max_metric'}, + {'name': 'router_lsa_stub', 'default': 16777215} + ] +] + +PROTOCOL_MAP = { + 'bgp': 'BGP', + 'kernel': 'KERNEL', + 'connected': 'DIRECTLY_CONNECTED', + 'static': 'STATIC', + 'default_route': 'DEFAULT_ROUTE' +} + +REDISTRIBUTE_DELETE_PATH = '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}' +OSPF_HELPER_PATH = '/global/graceful-restart/openconfig-ospfv2-ext:helpers/helper' +OSPF_ATTRIBUTES = { + 'abr_type': '/global/config/openconfig-ospfv2-ext:abr-type', + 'auto_cost_reference_bandwidth': '/global/config/openconfig-ospfv2-ext:auto-cost-reference-bandwidth', + 'default_metric': '/global/config/openconfig-ospfv2-ext:default-metric', + 'default_passive': '/global/config/openconfig-ospfv2-ext:passive-interface-default', + 'distance': { + 'all': '/global/openconfig-ospfv2-ext:distance/config/all', + 'external': '/global/openconfig-ospfv2-ext:distance/config/external', + 'inter_area': '/global/openconfig-ospfv2-ext:distance/config/inter-area', + 'intra_area': '/global/openconfig-ospfv2-ext:distance/config/intra-area' + }, + 'graceful_restart': { + 'grace_period': '/global/graceful-restart/config/openconfig-ospfv2-ext:grace-period', + 'enable': '/global/graceful-restart/config/enabled', + 'helper': { + 'enable': '/global/graceful-restart/config/helper-only', + 'planned_only': '/global/graceful-restart/config/openconfig-ospfv2-ext:planned-only', + 'strict_lsa_checking': '/global/graceful-restart/config/openconfig-ospfv2-ext:strict-lsa-checking', + 'supported_grace_time': '/global/graceful-restart/config/openconfig-ospfv2-ext:supported-grace-time', + 'advertise_router_id': '/global/graceful-restart/openconfig-ospfv2-ext:helpers/neighbour-id' + } + }, + 'log_adjacency_changes': '/global/config/openconfig-ospfv2-ext:log-adjacency-state-changes', + 'max_metric': { + 'administrative': '/global/timers/max-metric/config/openconfig-ospfv2-ext:administrative', + 'external_lsa_all': '/global/timers/max-metric/config/openconfig-ospfv2-ext:external-lsa-all', + 'external_lsa_connected': '/global/timers/max-metric/config/openconfig-ospfv2-ext:external-lsa-connected', + 'router_lsa_all': '/global/timers/max-metric/config/openconfig-ospfv2-ext:router-lsa-all', + 'router_lsa_stub': '/global/timers/max-metric/config/openconfig-ospfv2-ext:router-lsa-stub', + 'on_startup': '/global/timers/max-metric/config/openconfig-ospfv2-ext:on-startup' + }, + 'maximum_paths': '/global/config/openconfig-ospfv2-ext:maximum-paths', + 'non_passive_interfaces': { + 'interface': '/global/openconfig-ospfv2-ext:passive-interfaces/passive-interface={},{},{}', + 'addresses': '/global/openconfig-ospfv2-ext:passive-interfaces/passive-interface={},{},{}' + }, + 'opaque_lsa_capability': '/global/config/openconfig-ospfv2-ext:opaque-lsa-capability', + 'passive_interfaces': { + 'interface': '/global/openconfig-ospfv2-ext:passive-interfaces/passive-interface={},{},{}', + 'addresses': '/global/openconfig-ospfv2-ext:passive-interfaces/passive-interface={},{},{}' + }, + 'redistribute': { + 'always': '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}/config/always', + 'protocol': '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}', + 'metric': '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}/config/metric', + 'metric_type': '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}/config/metric-type', + 'route_map': '/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list={},{}/config/route-map' + }, + 'refresh_timer': '/global/timers/lsa-generation/config/openconfig-ospfv2-ext:refresh-timer', + 'rfc1583_compatible': '/global/config/openconfig-ospfv2-ext:ospf-rfc1583-compatible', + 'router_id': '/global/config/router-id', + 'timers': { + 'lsa_min_arrival': '/global/timers/lsa-generation/config/openconfig-ospfv2-ext:minimum-arrival', + 'throttle_lsa_all': '/global/timers/lsa-generation/config/openconfig-ospfv2-ext:minimum-interval', + 'throttle_spf': { + 'initial_hold_time': '/global/timers/spf/config/initial-delay', + 'maximum_hold_time': '/global/timers/spf/config/maximum-delay', + 'delay_time': '/global/timers/spf/config/openconfig-ospfv2-ext:throttle-delay' + } + }, + 'write_multiplier': '/global/config/openconfig-ospfv2-ext:write-multiplier', +} + + +class Ospfv2(ConfigBase): + """ + The sonic_ospfv2 class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ospfv2', + ] + + def __init__(self, module): + super(Ospfv2, self).__init__(module) + + def get_ospfv2_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + ospfv2_facts = facts['ansible_network_resources'].get('ospfv2') + if not ospfv2_facts: + return [] + return ospfv2_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + existing_ospfv2_facts = self.get_ospfv2_facts() + commands, requests = self.set_config(existing_ospfv2_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + result['commands'] = commands + + changed_ospfv2_facts = self.get_ospfv2_facts() + + result['before'] = existing_ospfv2_facts + if result['changed']: + result['after'] = changed_ospfv2_facts + + new_config = changed_ospfv2_facts + old_config = existing_ospfv2_facts + if self._module.check_mode: + result.pop('after', None) + existing_ospfv2_facts = remove_empties_from_list(existing_ospfv2_facts) + is_overridden = False + for cmd in commands: + if cmd['state'] == 'overridden': + is_overridden = True + break + if is_overridden: + new_config = get_new_config(commands, existing_ospfv2_facts, TEST_KEYS_overridden_diff) + else: + new_config = get_new_config(commands, existing_ospfv2_facts, TEST_KEYS_diff) + new_config = remove_empties_from_list(new_config) + result['after(generated)'] = self._post_process_generated_output(new_config) + self.sort_lists_in_config(result['after(generated)']) + + if self._module._diff: + result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + + result['warnings'] = warnings + return result + + def set_config(self, existing_ospfv2_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_ospfv2_facts + want = remove_empties_from_list(want) + new_want, new_have = self.validate_and_normalize_config(want, have) + self.sort_lists_in_config(new_want) + self.sort_lists_in_config(new_have) + resp = self.set_state(new_want, new_have) + return to_list(resp) + + def validate_and_normalize_config(self, want, have): + """ Validate the configuration + """ + new_want = deepcopy(want) + new_have = deepcopy(have) + for cfg in new_have: + self._normalize_passive_intf(cfg) + if new_want: + for cmd in new_want: + have_cmd = next((cfg for cfg in new_have if cfg['vrf_name'] == cmd['vrf_name']), None) + state = self._module.params['state'] + self._normalize_passive_intf(cmd) + want_default_passive = cmd.get('default_passive') + want_non_passive = cmd.get('non_passive_interfaces', []) + want_passive = cmd.get('passive_interfaces', []) + if state == 'merged': + if have_cmd: + have_default_passive = have_cmd.get('default_passive') + have_passive = have_cmd.get('passive_interfaces', []) + if want_passive and ((want_default_passive is None and have_default_passive) or want_default_passive): + self._module.fail_json(msg='Passive-interface default is configured. All interfaces are passive by default') + if want_non_passive and ((want_default_passive is None and not have_default_passive) or want_default_passive is False): + self._module.fail_json(msg='Passive-interface default is not configured. All interfaces are non-passive by default') + + if (have_default_passive and want_passive) or ((want_non_passive or want_default_passive) and have_passive): + self._module.fail_json(msg='Passive and non-passive interfaces cannot be configured together') + else: + if want_passive and want_default_passive: + self._module.fail_json(msg='Passive-interface default is configured. All interfaces are passive by default') + if want_non_passive and want_default_passive is False: + self._module.fail_json(msg='Passive-interface default is not configured. All interfaces are non-passive by default') + elif state != 'deleted': + if want_default_passive and want_passive: + self._module.fail_json(msg='If Passive-interface default is configured then all interfaces are passive by default') + if want_default_passive is False and want_non_passive: + self._module.fail_json(msg='If Passive-interface default is not configured then all interfaces are non-passive by default') + if want_default_passive is None: + if want_non_passive: + cmd['default_passive'] = True + elif want_passive: + cmd['default_passive'] = False + + graceful_restart = cmd.get('graceful_restart') + if state != 'deleted': + if graceful_restart: + if graceful_restart.get('enable') is None: + cmd['graceful_restart']['enable'] = True + if graceful_restart.get('helper') is not None: + if graceful_restart['helper'].get('enable') is None: + cmd['graceful_restart']['helper']['enable'] = True + else: + if have_cmd: + have_graceful_restart = have_cmd.get('graceful_restart') + if graceful_restart: + if "enable" in graceful_restart and 'grace_period' not in graceful_restart: + if have_graceful_restart and 'grace_period' in have_graceful_restart: + cmd['graceful_restart']['grace_period'] = have_graceful_restart['grace_period'] + + return new_want, new_have + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + state = self._module.params['state'] + if state == 'overridden': + commands, requests = self._state_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + elif state == 'replaced': + commands, requests = self._state_replaced(want, have) + return commands, requests + + def _state_replaced(self, want, have): + """ The command generator when state is replaced + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + add_config, del_config = self._get_replaced_config(want, have) + if del_config: + del_commands, del_requests = self.get_delete_ospfv2_commands_requests(del_config, have, True) + if len(del_requests) > 0: + requests.extend(del_requests) + commands.extend(update_states(del_commands, 'deleted')) + if add_config: + mod_requests = self.get_create_ospfv2_requests(add_config) + if len(mod_requests) > 0: + requests.extend(mod_requests) + commands.extend(update_states(add_config, 'replaced')) + return commands, requests + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + diff = get_diff(want, have, TEST_KEYS) + diff2 = get_diff(have, want, TEST_KEYS) + if diff or diff2: + del_commands, del_requests = self.get_delete_ospfv2_commands_requests(have, have, True) + if len(del_requests) > 0: + requests.extend(del_requests) + commands.extend(update_states(have, 'deleted')) + mod_requests = self.get_create_ospfv2_requests(want) + if len(mod_requests) > 0: + requests.extend(mod_requests) + commands.extend(update_states(want, 'overridden')) + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = get_diff(want, have, TEST_KEYS) + requests = self.get_create_ospfv2_requests(commands) + + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands, requests = [], [] + is_delete_all = False + + if not want: + new_want = have + is_delete_all = True + else: + self.sort_lists_in_config(want) + self.sort_lists_in_config(have) + new_want = deepcopy(want) + new_have = deepcopy(have) + for default_entry in DEFAULT_ENTRIES: + remove_matching_defaults(new_have, default_entry) + new_have = remove_empties_from_list(new_have) + new_want = remove_empties_from_list(new_want) + commands, requests = self.get_delete_ospfv2_commands_requests(new_want, have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') + else: + commands = [] + return commands, requests + + def _get_replaced_config(self, want, have): + add_config, del_config = [], [] + for conf in want: + is_change = False + vrf_name = conf.get('vrf_name') + have_conf = next((cfg for cfg in have if cfg['vrf_name'] == vrf_name), None) + if not have_conf: + add_config.append(conf) + else: + for attr in OSPF_ATTRIBUTES: + if attr in have_conf: + if attr not in conf: + is_change = True + break + else: + if attr == 'redistribute': + for redis_list in conf.get(attr, []): + protocol = redis_list.get('protocol') + have_redis = next((redis for redis in have_conf.get(attr, []) if redis['protocol'] == protocol), None) + if not have_redis or (have_redis and have_redis != redis_list): + is_change = True + break + elif attr in ('passive_interfaces', 'non_passive_interfaces'): + for interface in conf.get(attr, []): + interface_name = interface.get('interface') + have_interface = next((intf for intf in have_conf.get(attr, []) if intf['interface'] == interface_name), None) + if not have_interface or (have_interface and have_interface != interface): + is_change = True + break + elif attr != 'vrf_name': + if have_conf[attr] != conf[attr]: + is_change = True + break + elif attr in conf: + is_change = True + break + if is_change: + add_config.append(conf) + del_config.append(have_conf) + return add_config, del_config + + def get_create_ospfv2_requests(self, commands): + requests = [] + + for cmd in commands: + vrf_name = cmd.get('vrf_name') + sub_commands = deepcopy(cmd) + sub_commands.pop('vrf_name') + payload = {} + self._get_create_payload_from_dict(sub_commands, OSPF_ATTRIBUTES, payload) + if payload: + requests.append({'path': OSPF_PATH.format(vrf_name=vrf_name), 'method': PATCH, 'data': {'openconfig-network-instance:ospfv2': payload}}) + return requests + + def _get_create_payload_from_dict(self, command, ospf_dict, payload): + for attr in ospf_dict: + if attr in command and command[attr] is not None: + if not isinstance(ospf_dict[attr], dict): + path = ospf_dict[attr] + request_body = path.split('/') + last_body = request_body[-1] + payload_iter = payload + for body in request_body[1:-1]: + payload_iter.setdefault(body, {}) + payload_iter = payload_iter[body] + if attr == 'advertise_router_id': + if command['advertise_router_id']: + payload_iter.setdefault('helper', []) + for ip in command['advertise_router_id']: + payload_iter['helper'].append({'neighbour-id': ip, 'config': {'neighbour-id': ip}}) + elif attr in ('abr_type', 'log_adjacency_changes'): + payload_iter[last_body] = 'openconfig-ospfv2-ext:' + command[attr].upper() + else: + if attr == 'grace_period': + command['enable'] = True + payload_iter['enabled'] = True + payload_iter[last_body] = command[attr] + elif attr == 'redistribute': + self._get_create_redistribute_payload(command[attr], ospf_dict[attr], payload) + elif attr in ('passive_interfaces', 'non_passive_interfaces'): + self._get_create_passive_interfaces_payload(command[attr], attr, payload) + else: + self._get_create_payload_from_dict(command[attr], ospf_dict[attr], payload) + + def _get_create_redistribute_payload(self, command, ospf_dict, payload): + protocol = 'DEFAULT_ROUTE' + for redistribute_list in command: + if 'protocol' in redistribute_list: + payload.setdefault('global', {}) + payload['global'].setdefault('openconfig-ospfv2-ext:route-distribution-policies', {}) + payload['global']['openconfig-ospfv2-ext:route-distribution-policies'].setdefault('distribute-list', []) + protocol = PROTOCOL_MAP[redistribute_list['protocol']] + distribute_payload = {'protocol': protocol, 'direction': DIRECTION} + if len(redistribute_list) == 1: + payload['global']['openconfig-ospfv2-ext:route-distribution-policies']['distribute-list'].append(distribute_payload) + else: + for item in redistribute_list: + if item not in ('protocol', 'metric_type'): + if item == 'always' and protocol != 'DEFAULT_ROUTE': + continue + config_type = ospf_dict[item].split('/')[-1] + distribute_payload.setdefault('config', {}) + distribute_payload['config'][config_type] = redistribute_list[item] + elif item == 'metric_type': + distribute_payload.setdefault('config', {}) + metric_type = 'TYPE_1' if redistribute_list[item] == 1 else 'TYPE_2' + distribute_payload['config']['openconfig-ospfv2-ext:metric-type'] = metric_type + payload['global']['openconfig-ospfv2-ext:route-distribution-policies']['distribute-list'].append(distribute_payload) + + def _get_create_passive_interfaces_payload(self, command, attr, payload): + non_passive = True if attr == 'non_passive_interfaces' else False + body = {'openconfig-ospfv2-ext:non-passive': non_passive} + for intf in command: + if 'interface' in intf: + parent_intf, sub_intf = intf['interface'].split('.') if '.' in intf['interface'] else (intf['interface'], 0) + payload.setdefault('global', {}) + payload['global'].setdefault('openconfig-ospfv2-ext:passive-interfaces', {}) + payload['global']['openconfig-ospfv2-ext:passive-interfaces'].setdefault('passive-interface', []) + for address in intf.get('addresses', []): + request_body = { + 'name': parent_intf, + 'subinterface': sub_intf, + 'address': address, + 'config': body + } + payload['global']['openconfig-ospfv2-ext:passive-interfaces']['passive-interface'].append(request_body) + + if not payload['global']['openconfig-ospfv2-ext:passive-interfaces']['passive-interface']: + intf['addresses'] = [DEFAULT_ADDRESS] + payload['global']['openconfig-ospfv2-ext:passive-interfaces']['passive-interface'].append( + { + 'name': parent_intf, + 'subinterface': sub_intf, + 'address': DEFAULT_ADDRESS, + 'config': body + } + ) + + def get_delete_ospfv2_commands_requests(self, want, have, is_delete_all): + commands, requests = [], [] + for cmd in want: + del_cmd, request = {}, {} + vrf_name = cmd.get('vrf_name') + sub_commands = deepcopy(cmd) + sub_commands.pop('vrf_name') + url = OSPF_PATH.format(vrf_name=vrf_name) + if is_delete_all: + commands.append({'vrf_name': vrf_name}) + requests.append({'path': url + '/global', 'method': DELETE}) + elif sub_commands == {}: + have_cmd = next((cfg for cfg in have if cfg['vrf_name'] == vrf_name), None) + if have_cmd: + commands.append({'vrf_name': vrf_name}) + requests.append({'path': url + '/global', 'method': DELETE}) + else: + have_cmd = next((cfg for cfg in have if cfg['vrf_name'] == vrf_name), None) + if have_cmd: + del_cmd, request = self._get_delete_commands_requests_path_from_dict(sub_commands, have_cmd, OSPF_ATTRIBUTES, url) + if del_cmd: + commands.append(del_cmd) + requests.extend(request) + return commands, requests + + def _get_delete_commands_requests_path_from_dict(self, want, have, ospf_dict, ospf_path): + commands, requests = {}, [] + for attr in ospf_dict: + del_attr, request = {}, {} + if attr in want and attr in have: + if not isinstance(ospf_dict[attr], dict): + if attr == 'advertise_router_id': + adv_attr = [] + for ip in want['advertise_router_id']: + if ip in have['advertise_router_id']: + adv_attr.append(ip) + url = '%s%s=%s' % (ospf_path, OSPF_HELPER_PATH, ip) + requests.append({'path': url, 'method': DELETE}) + if adv_attr: + commands['advertise_router_id'] = adv_attr + elif None not in [want.get(attr), have.get(attr)] and want[attr] == have[attr]: + commands[attr] = want[attr] + requests.append({'path': ospf_path + ospf_dict[attr], 'method': DELETE}) + elif attr == 'redistribute': + del_attr, request = self._get_delete_redistribute_commands_requests(want[attr], have[attr], ospf_dict[attr], ospf_path) + if del_attr: + commands['redistribute'] = del_attr + requests.extend(request) + elif attr in ('passive_interfaces', 'non_passive_interfaces'): + del_attr, request = self._get_delete_passive_interfaces_commands_requests(want[attr], have[attr], ospf_dict[attr], attr, ospf_path) + if del_attr: + commands[attr] = del_attr + requests.extend(request) + else: + del_attr, request = self._get_delete_commands_requests_path_from_dict(want[attr], have[attr], ospf_dict[attr], ospf_path) + if del_attr: + commands[attr] = del_attr + requests.extend(request) + return commands, requests + + def _get_delete_redistribute_commands_requests(self, want, have, ospf_dict, ospf_path): + commands, requests = [], [] + for redistribute_list in want: + protocol = redistribute_list['protocol'] + have_redistribute_list = next((cfg for cfg in have if cfg['protocol'] == redistribute_list['protocol']), None) + if have_redistribute_list: + if protocol != 'default_route': + url = ospf_path + ospf_dict['protocol'] + commands.append({'protocol': protocol}) + requests.append({'path': url.format(PROTOCOL_MAP[protocol], DIRECTION), 'method': DELETE}) + else: + if len(redistribute_list) == 1: + url = ospf_path + ospf_dict['protocol'] + commands.append({'protocol': protocol}) + requests.append({'path': url.format(PROTOCOL_MAP[protocol], DIRECTION), 'method': DELETE}) + else: + for item in redistribute_list: + redis_cmd = {} + if item != 'protocol' and item in have_redistribute_list and redistribute_list[item] == have_redistribute_list[item]: + url = ospf_path + ospf_dict[item] + redis_cmd[item] = have_redistribute_list[item] + requests.append({'path': url.format(PROTOCOL_MAP[protocol], DIRECTION), 'method': DELETE}) + if redis_cmd: + redis_cmd['protocol'] = protocol + commands.append(redis_cmd) + return commands, requests + + def _get_delete_passive_interfaces_commands_requests(self, want, have, ospf_dict, attr, ospf_path): + non_passive = True if attr == 'non_passive_interfaces' else False + body = {'openconfig-ospfv2-ext:non-passive': False} + request_body = { + 'openconfig-network-instance:ospfv2': { + 'global': { + 'openconfig-ospfv2-ext:passive-interfaces': { + 'passive-interface': [] + } + } + } + } + passive_intf = request_body['openconfig-network-instance:ospfv2']['global']['openconfig-ospfv2-ext:passive-interfaces']['passive-interface'] + commands, requests = [], [] + for intf in want: + if 'interface' in intf: + have_intf = next((cfg for cfg in have if cfg['interface'] == intf['interface']), None) + if have_intf: + intf_name = intf['interface'] + parent_intf, sub_intf = intf_name.split('.') if '.' in intf_name else (intf_name, 0) + address_list = [] + if len(intf) == 1: + for address in have_intf['addresses']: + address_list.append(address) + if non_passive: + passive_intf.append({ + 'name': parent_intf, + 'subinterface': sub_intf, + 'address': address, + 'config': body + }) + else: + url = ospf_path + ospf_dict['interface'].format(parent_intf.replace('/', '%2f'), sub_intf, address) + requests.append({'path': url, 'method': DELETE}) + else: + address_list = [] + for address in intf['addresses']: + if address in have_intf['addresses']: + address_list.append(address) + if non_passive: + passive_intf.append({ + 'name': parent_intf, + 'subinterface': sub_intf, + 'address': address, + 'config': body + }) + else: + url = ospf_path + ospf_dict['interface'].format(parent_intf.replace('/', '%2f'), sub_intf, address) + requests.append({'path': url, 'method': DELETE}) + if address_list: + commands.append({'interface': intf_name, 'addresses': address_list}) + + if passive_intf: + requests.append({'path': ospf_path, 'method': PATCH, 'data': request_body}) + return commands, requests + + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: x['vrf_name']) + for cfg in config: + if cfg.get('passive_interfaces', None): + cfg['passive_interfaces'].sort(key=lambda x: x['interface']) + for intf in cfg['passive_interfaces']: + intf_addresses = intf.get('addresses', []) + if intf_addresses: + intf_addresses.sort() + if cfg.get('non_passive_interfaces', None): + cfg['non_passive_interfaces'].sort(key=lambda x: x['interface']) + for intf in cfg['non_passive_interfaces']: + intf_addresses = intf.get('addresses', []) + if intf_addresses: + intf_addresses.sort() + if cfg.get('redistribute', None): + cfg['redistribute'].sort(key=lambda x: x['protocol']) + if cfg.get('graceful_restart', None): + if cfg['graceful_restart'].get('helper', None): + if cfg['graceful_restart']['helper'].get('advertise_router_id', []): + cfg['graceful_restart']['helper']['advertise_router_id'].sort() + + def _normalize_passive_intf(self, cmd): + for intf in cmd.get('non_passive_interfaces', []): + intf['interface'] = get_normalize_interface_name(intf['interface'], self._module) + if not intf.get('addresses', []): + intf['addresses'] = [DEFAULT_ADDRESS] + + for intf in cmd.get('passive_interfaces', []): + intf['interface'] = get_normalize_interface_name(intf['interface'], self._module) + if not intf.get('addresses', []): + intf['addresses'] = [DEFAULT_ADDRESS] + + def _post_process_generated_output(self, config): + for cmd in config: + if "non_passive_interfaces" in cmd: + cmd['default_passive'] = True + non_passive_intf = [] + for intf in cmd.get('non_passive_interfaces', []): + if "addresses" in intf: + non_passive_intf.append(intf) + if non_passive_intf: + cmd['non_passive_interfaces'] = non_passive_intf + else: + cmd['default_passive'] = False + passive_intf = [] + for intf in cmd.get('passive_interfaces', []): + if "addresses" in intf: + passive_intf.append(intf) + if passive_intf: + cmd['passive_interfaces'] = passive_intf + return config diff --git a/plugins/module_utils/network/sonic/config/ospfv2_interfaces/__init__.py b/plugins/module_utils/network/sonic/config/ospfv2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/ospfv2_interfaces/ospfv2_interfaces.py b/plugins/module_utils/network/sonic/config/ospfv2_interfaces/ospfv2_interfaces.py new file mode 100644 index 000000000..f53f4db46 --- /dev/null +++ b/plugins/module_utils/network/sonic/config/ospfv2_interfaces/ospfv2_interfaces.py @@ -0,0 +1,759 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_ospfv2_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +import ipaddress +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + update_states, + get_diff, + normalize_interface_name, + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + get_new_config, + get_formatted_config_diff, + __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF +) +from ansible.module_utils.connection import ConnectionError + +PATCH = 'patch' +DELETE = 'delete' +DEFAULT_ADDRESS = '0.0.0.0' + +TEST_KEYS = [ + {'config': {'name': ''}}, + {'ospf_attributes': {'address': ''}}, + {'md_authentication': {'key_id': ''}} +] + +TEST_KEYS_overridden_diff = [ + {'config': {'name': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'ospf_attributes': {'address': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'md_authentication': {'key_id': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}} +] + +OSPF_INT_ATTRIBUTES = { + 'bfd': { + 'enable': '/if-addresses={}/enable-bfd', + 'bfd_profile': '/if-addresses={}/enable-bfd/config/bfd-profile' + }, + 'network': '/if-addresses={}/config/network-type', + 'ospf_attributes': { + 'address': '/if-addresses={}', + 'area_id': '/if-addresses={}/config/area-id', + 'authentication': '/if-addresses={}/config/authentication-key', + 'authentication_type': '/if-addresses={}/config/authentication-type', + 'cost': '/if-addresses={}/config/metric', + 'dead_interval': '/if-addresses={}/config/dead-interval', + 'hello_interval': '/if-addresses={}/config/hello-interval', + 'hello_multiplier': '/if-addresses={}/config/dead-interval-minimal', + 'md_authentication': '/if-addresses={}/md-authentications/md-authentication={}', + 'mtu_ignore': '/if-addresses={}/config/mtu-ignore', + 'priority': '/if-addresses={}/config/priority', + 'retransmit_interval': '/if-addresses={}/config/retransmission-interval', + 'transmit_delay': '/if-addresses={}/config/transmit-delay' + } + +} + + +class Ospfv2_interfaces(ConfigBase): + """ + The sonic_ospfv2_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ospfv2_interfaces', + ] + + def __init__(self, module): + super(Ospfv2_interfaces, self).__init__(module) + + def get_ospfv2_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + ospfv2_interfaces_facts = facts['ansible_network_resources'].get('ospfv2_interfaces') + if not ospfv2_interfaces_facts: + return [] + return ospfv2_interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + + existing_ospfv2_interfaces_facts = self.get_ospfv2_interfaces_facts() + commands, requests = self.set_config(existing_ospfv2_interfaces_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + result['commands'] = commands + + changed_ospfv2_interfaces_facts = self.get_ospfv2_interfaces_facts() + + result['before'] = existing_ospfv2_interfaces_facts + if result['changed']: + result['after'] = changed_ospfv2_interfaces_facts + + new_config = changed_ospfv2_interfaces_facts + old_config = existing_ospfv2_interfaces_facts + if self._module.check_mode: + result.pop('after', None) + new_commands = deepcopy(commands) + self._add_default_address(new_commands) + self._add_default_address(new_config) + self._add_default_address(old_config) + + if self._module.params['state'] == 'overridden': + new_config = get_new_config(new_commands, old_config, TEST_KEYS_overridden_diff) + else: + new_config = get_new_config(new_commands, old_config, TEST_KEYS) + self._add_default_address(new_config) + self.sort_lists_in_config(new_config) + new_config = self._get_generated_config(new_commands, new_config, self._module.params['state']) + self._strip_default_address(new_config) + self._strip_default_address(old_config) + result['after(generated)'] = remove_empties_from_list(new_config) + + if self._module._diff: + result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + + result['warnings'] = warnings + return result + + def set_config(self, existing_ospfv2_interfaces_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_ospfv2_interfaces_facts + new_want = deepcopy(want) + new_have = deepcopy(have) + new_want = remove_empties_from_list(want) + new_have = remove_empties_from_list(have) + self._add_default_address(new_want) + self._add_default_address(new_have) + self.sort_lists_in_config(new_want) + self.sort_lists_in_config(new_have) + new_want = self._normalize_interface_name(new_want) + resp = self.set_state(new_want, new_have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + state = self._module.params['state'] + + if state == 'overridden' or state == 'replaced': + commands, requests = self._state_replaced_or_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + + return commands, requests + + def _state_replaced_or_overridden(self, want, have): + """ The command generator when state is replaced or overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + add_config, del_config = self._get_replaced_overridden_config(want, have) + if del_config: + del_commands, del_requests = self.get_delete_ospf_interfaces_commands_requests(del_config, have, False) + if len(del_requests) > 0: + self._strip_default_address(del_commands) + commands.extend(update_states(del_commands, 'deleted')) + requests.extend(del_requests) + + if add_config: + mod_requests = self.get_create_ospf_interfaces_requests(add_config, []) + if len(mod_requests) > 0: + self._strip_default_address(add_config) + commands.extend(update_states(add_config, self._module.params['state'])) + requests.extend(mod_requests) + + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = get_diff(want, have) + requests = self.get_create_ospf_interfaces_requests(commands, have) + + if commands and len(requests) > 0: + self._strip_default_address(commands) + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands, requests = [], [] + is_delete_all = False + + if not want: + commands = have + is_delete_all = True + else: + commands = want + + del_commands, requests = self.get_delete_ospf_interfaces_commands_requests(commands, have, is_delete_all) + + if del_commands and len(requests) > 0: + self._strip_default_address(del_commands) + commands = update_states(del_commands, 'deleted') + else: + commands = [] + + return commands, requests + + def _get_replaced_overridden_config(self, want, have): + add_config, del_config = [], [] + state = self._module.params['state'] + for conf in want: + intf_name = conf.get('name') + have_conf = next((cfg for cfg in have if cfg['name'] == intf_name), None) + if not have_conf: + add_config.append(conf) + else: + add_cfg, del_cfg = {}, {} + for attr in OSPF_INT_ATTRIBUTES: + if attr in conf: + if attr not in have_conf: + add_cfg[attr] = conf[attr] + else: + if attr == 'bfd': + for bfd_attr in ['enable', 'bfd_profile']: + if bfd_attr in conf[attr]: + if bfd_attr not in have_conf[attr]: + add_cfg.setdefault(attr, {})[bfd_attr] = conf[attr][bfd_attr] + elif conf[attr][bfd_attr] != have_conf[attr][bfd_attr]: + add_cfg.setdefault(attr, {})[bfd_attr] = conf[attr][bfd_attr] + elif bfd_attr in have_conf[attr]: + del_cfg.setdefault(attr, {})[bfd_attr] = have_conf[attr][bfd_attr] + elif attr == 'network': + if conf[attr] != have_conf[attr]: + add_cfg[attr] = conf[attr] + else: + # attr = 'ospf_attributes' + ospf_attr = conf[attr] + match_ospf_attr = have_conf.get(attr, []) + for ospf_list in ospf_attr: + address = ospf_list.get('address') + match_ospf_list = next((o_list for o_list in match_ospf_attr if o_list.get('address') == address), None) + if not match_ospf_list: + add_cfg.setdefault(attr, []).append(ospf_list) + else: + add_ospf_attr, del_ospf_attr = self._get_replaced_config_for_ospf_attributes(ospf_list, match_ospf_list) + if add_ospf_attr: + add_ospf_attr['address'] = address + add_cfg.setdefault(attr, []).append(add_ospf_attr) + if del_ospf_attr: + del_ospf_attr['address'] = address + del_cfg.setdefault(attr, []).append(del_ospf_attr) + for match_ospf_list in match_ospf_attr: + address = match_ospf_list.get('address') + ospf_list = next((o_list for o_list in ospf_attr if o_list.get('address') == address), None) + if not ospf_list: + del_cfg.setdefault(attr, []).append({'address': address}) + elif attr in have_conf: + del_cfg[attr] = have_conf[attr] + if add_cfg: + add_cfg['name'] = intf_name + add_config.append(add_cfg) + if del_cfg: + del_cfg['name'] = intf_name + del_config.append(del_cfg) + + if state == 'overridden': + for conf in have: + intf_name = conf.get('name') + want_conf = next((cfg for cfg in want if cfg['name'] == intf_name), None) + if not want_conf: + del_config.append({'name': intf_name}) + return add_config, del_config + + def _get_replaced_config_for_ospf_attributes(self, want, have): + add_config, del_config = {}, {} + for ospf_attr in OSPF_INT_ATTRIBUTES['ospf_attributes']: + if ospf_attr == 'hello_multiplier': + if ospf_attr in want: + if ospf_attr not in have: + add_config[ospf_attr] = want[ospf_attr] + if 'dead_interval' in have: + del_config['dead_interval'] = have['dead_interval'] + elif want[ospf_attr] != have[ospf_attr]: + add_config[ospf_attr] = want[ospf_attr] + elif ospf_attr in have: + del_config[ospf_attr] = have[ospf_attr] + elif ospf_attr == 'dead_interval': + if ospf_attr in want: + if ospf_attr not in have: + add_config[ospf_attr] = want[ospf_attr] + if 'hello_interval' in have: + del_config['hello_interval'] = have['hello_interval'] + elif want[ospf_attr] != have[ospf_attr]: + add_config[ospf_attr] = want[ospf_attr] + elif ospf_attr in have: + del_config[ospf_attr] = have[ospf_attr] + elif ospf_attr in want: + if ospf_attr not in have: + add_config[ospf_attr] = want[ospf_attr] + elif want[ospf_attr] != have[ospf_attr]: + if ospf_attr != 'md_authentication': + add_config[ospf_attr] = want[ospf_attr] + if ospf_attr == 'area_id': + del_config['area_id'] = have['area_id'] + else: + have_mdkeys = have[ospf_attr] + conf_mdkeys = want[ospf_attr] + add_mdkeys, del_mdkeys = [], [] + for conf_key in conf_mdkeys: + match_key = next((key for key in have_mdkeys if key['key_id'] == conf_key['key_id']), None) + if match_key: + if match_key['pwd'] != conf_key['pwd']: + add_mdkeys.append(conf_key) + del_mdkeys.append(match_key) + else: + add_mdkeys.append(conf_key) + for match_key in have_mdkeys: + conf_key = next((key for key in conf_mdkeys if key['key_id'] == match_key['key_id']), None) + if not conf_key: + del_mdkeys.append(match_key) + if add_mdkeys: + add_config[ospf_attr] = add_mdkeys + if del_mdkeys: + del_config[ospf_attr] = del_mdkeys + elif ospf_attr in have: + del_config[ospf_attr] = have[ospf_attr] + + return add_config, del_config + + def get_create_ospf_interfaces_requests(self, commands, have): + requests = [] + bfd_dict = {} + if not commands: + return requests + + for cmd in commands: + payload = {} + bfd_dict = {} + name = cmd.get('name') + match = next((item for item in have if item['name'] == cmd['name']), None) + intf_name, sub_intf = self.get_ospf_if_and_subif(name) + ospf_path = self.get_ospf_uri(intf_name, sub_intf) + ospf_attr_configs = [] + default_address_attr_dict = {} + network_type = "" + for attr in cmd: + if attr == 'name': + continue + if attr == 'ospf_attributes': + for ospf_list in cmd.get(attr, []): + ospf_attr_dict = {} + ospf_md_configs_list = [] + address = ospf_list.get('address') + area_id = ospf_list.get('area_id') + if address and area_id and match: + for match_attr in match.get('ospf_attributes', []): + match_address = match_attr.get('address') + match_area_id = match_attr.get('area_id') + if match_address and match_area_id and match_address == address and match_area_id != area_id: + path = ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes']['area_id'].format(address) + requests.append({'path': path, 'method': DELETE}) + break + + self.update_dict(ospf_list, ospf_attr_dict, 'area_id', 'area-id') + self.update_dict(ospf_list, ospf_attr_dict, 'authentication_type', 'authentication-type') + self.update_dict(ospf_list, ospf_attr_dict, 'cost', 'metric') + self.update_dict(ospf_list, ospf_attr_dict, 'dead_interval', 'dead-interval') + self.update_dict(ospf_list, ospf_attr_dict, 'hello_interval', 'hello-interval') + self.update_dict(ospf_list, ospf_attr_dict, 'hello_multiplier', 'hello-multiplier') + if 'hello-multiplier' in ospf_attr_dict: + ospf_attr_dict['dead-interval-minimal'] = True + self.update_dict(ospf_list, ospf_attr_dict, 'mtu_ignore', 'mtu-ignore') + self.update_dict(ospf_list, ospf_attr_dict, 'priority', 'priority') + self.update_dict(ospf_list, ospf_attr_dict, 'retransmit_interval', 'retransmission-interval') + self.update_dict(ospf_list, ospf_attr_dict, 'transmit_delay', 'transmit-delay') + + if 'authentication' in ospf_list: + self.update_dict(ospf_list['authentication'], ospf_attr_dict, 'password', 'authentication-key') + self.update_dict(ospf_list['authentication'], ospf_attr_dict, 'encrypted', 'authentication-key-encrypted') + + if 'md_authentication' in ospf_list: + for mdkeys in ospf_list['md_authentication']: + md_config = {} + self.update_dict(mdkeys, md_config, 'key_id', 'authentication-key-id') + if md_config: + md_config.setdefault('config', {}) + self.update_dict(mdkeys, md_config['config'], 'key_id', 'authentication-key-id') + self.update_dict(mdkeys, md_config['config'], 'encrypted', 'authentication-key-encrypted') + self.update_dict(mdkeys, md_config['config'], 'md5key', 'authentication-md5-key') + if md_config: + ospf_md_configs_list.append(md_config) + + if ospf_attr_dict: + ospf_attr_dict = {'config': ospf_attr_dict} + if ospf_md_configs_list: + ospf_attr_dict['md-authentications'] = {'md-authentication': ospf_md_configs_list} + + if ospf_attr_dict: + if address == DEFAULT_ADDRESS: + default_address_attr_dict = ospf_attr_dict + else: + ospf_attr_dict.setdefault('config', {})['address'] = address + ospf_attr_dict['address'] = address + ospf_attr_configs.append(ospf_attr_dict) + elif attr == 'bfd': + self.update_dict(cmd[attr], bfd_dict, 'enable', 'enabled') + self.update_dict(cmd[attr], bfd_dict, 'bfd_profile', 'bfd-profile') + elif attr == 'network': + network_type = cmd.get('network') + if network_type: + network_type = 'openconfig-ospf-types:' + network_type.upper() + '_NETWORK' + + if default_address_attr_dict: + default_address_attr_dict.setdefault('config', {})['address'] = DEFAULT_ADDRESS + if network_type: + default_address_attr_dict['config']['network-type'] = network_type + default_address_attr_dict['address'] = DEFAULT_ADDRESS + ospf_attr_configs.append(default_address_attr_dict) + elif network_type: + ospf_attr_configs.append({ + 'address': DEFAULT_ADDRESS, + 'config': {'network-type': network_type, 'address': DEFAULT_ADDRESS} + }) + + if ospf_attr_configs: + payload = { + 'openconfig-ospfv2-ext:ospfv2': { + 'if-addresses': ospf_attr_configs + } + } + requests.append({'path': ospf_path, 'method': PATCH, 'data': payload}) + if bfd_dict: + payload = { + 'openconfig-ospfv2-ext:ospfv2': { + 'if-addresses': [{ + 'address': DEFAULT_ADDRESS, + 'enable-bfd': {'config': bfd_dict} + }] + } + } + requests.append({'path': ospf_path, 'method': PATCH, 'data': payload}) + return requests + + def get_delete_ospf_interfaces_commands_requests(self, commands, have, is_delete_all): + commands_del, requests = [], [] + if not commands: + return commands_del, requests + + for cmd in commands: + del_cmd = {} + name = cmd.get('name') + intf_name, sub_intf = self.get_ospf_if_and_subif(name) + ospf_path = self.get_ospf_uri(intf_name, sub_intf) + match_have = next((cfg for cfg in have if cfg['name'] == name), None) + if match_have: + if is_delete_all or len(cmd) == 1: + commands_del.append(match_have) + requests.append({'path': ospf_path, 'method': DELETE}) + continue + for attr in cmd: + if attr == 'name': + continue + if attr == 'bfd': + if 'enable' in cmd.get(attr, {}) and 'enable' in match_have.get(attr, {}): + path = ospf_path + OSPF_INT_ATTRIBUTES['bfd']['enable'].format(DEFAULT_ADDRESS) + requests.append({'path': path, 'method': DELETE}) + del_cmd.setdefault(attr, {})['enable'] = match_have[attr]['enable'] + if 'bfd_profile' in match_have.get(attr, {}): + del_cmd[attr]['bfd_profile'] = match_have[attr]['bfd_profile'] + elif 'bfd_profile' in cmd.get(attr, {}) and 'bfd_profile' in match_have.get(attr, {}): + path = ospf_path + OSPF_INT_ATTRIBUTES['bfd']['bfd_profile'].format(DEFAULT_ADDRESS) + requests.append({'path': path, 'method': DELETE}) + del_cmd.setdefault(attr, {})['bfd_profile'] = match_have[attr]['bfd_profile'] + elif attr == 'network' and match_have.get(attr, {}): + path = ospf_path + OSPF_INT_ATTRIBUTES['network'].format(DEFAULT_ADDRESS) + requests.append({'path': path, 'method': DELETE}) + del_cmd[attr] = match_have[attr] + elif attr == 'ospf_attributes': + match_ospf_attrs = match_have.get('ospf_attributes', []) + if match_ospf_attrs: + ospf_attrs = cmd.get('ospf_attributes') + if ospf_attrs is not None: + if not ospf_attrs: + # Delete all attributes in have + del_ospf_attrs, del_requests = self.get_delete_ospf_attributes_commands_requests(match_ospf_attrs, None, ospf_path) + requests.extend(del_requests) + if del_ospf_attrs: + del_cmd[attr] = del_ospf_attrs + else: + # Delete specific attributes in have + del_ospf_attrs, del_requests = self.get_delete_ospf_attributes_commands_requests(match_ospf_attrs, ospf_attrs, ospf_path) + requests.extend(del_requests) + if del_ospf_attrs: + del_cmd[attr] = del_ospf_attrs + if del_cmd: + del_cmd['name'] = name + commands_del.append(del_cmd) + return commands_del, requests + + def get_delete_ospf_attributes_commands_requests(self, match_ospf_attrs, ospf_attrs, ospf_path=''): + commands, requests = [], [] + if ospf_attrs: + for o_attr in ospf_attrs: + del_ospf_attr = {} + address = o_attr.get('address') + if address: + m_attr = next((cfg for cfg in match_ospf_attrs if cfg['address'] == address), None) + if m_attr: + if len(o_attr) == 1: + cmd, requests_to_delete = self.get_delete_all_ospf_attributes_per_address_commands_requests(m_attr, ospf_path) + requests.extend(requests_to_delete) + if cmd: + cmd['address'] = address + del_ospf_attr = cmd + commands.append(del_ospf_attr) + continue + for attr in o_attr: + if attr not in ('md_authentication', 'address'): + if attr in m_attr and o_attr[attr] is not None and m_attr[attr] is not None: + requests.append({ + 'path': ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes'][attr].format(address), + 'method': DELETE + }) + del_ospf_attr[attr] = m_attr[attr] + elif attr == 'md_authentication': + if o_attr.get(attr) is None: + del_mkeys = [] + for key in m_attr.get(attr, []): + requests.append({ + 'path': ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes'][attr].format(m_attr['address'], key['key_id']), + 'method': DELETE + }) + del_mkeys.append(key) + if del_mkeys: + del_ospf_attr[attr] = del_mkeys + + else: + del_mkeys = [] + for key in o_attr[attr]: + match_key = next((cfg for cfg in m_attr.get(attr, []) if cfg['key_id'] == key['key_id']), None) + if match_key: + requests.append({ + 'path': ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes'][attr].format(address, key['key_id']), + 'method': DELETE + }) + del_mkeys.append(match_key) + if del_mkeys: + del_ospf_attr[attr] = del_mkeys + if del_ospf_attr: + del_ospf_attr['address'] = address + commands.append(del_ospf_attr) + else: + for m_attr in match_ospf_attrs: + cmd, requests_to_delete = self.get_delete_all_ospf_attributes_per_address_commands_requests(m_attr, ospf_path) + requests.extend(requests_to_delete) + if cmd: + cmd['address'] = m_attr['address'] + commands.append(cmd) + + return commands, requests + + def get_delete_all_ospf_attributes_per_address_commands_requests(self, m_attr, ospf_path): + commands, requests = {}, [] + for attr in m_attr: + if attr not in ('md_authentication', 'address'): + requests.append({ + 'path': ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes'][attr].format(m_attr['address']), + 'method': DELETE + }) + commands[attr] = m_attr[attr] + elif attr == 'md_authentication': + del_mkeys = [] + for key in m_attr.get(attr, []): + requests.append({ + 'path': ospf_path + OSPF_INT_ATTRIBUTES['ospf_attributes'][attr].format(m_attr['address'], key['key_id']), + 'method': DELETE + }) + del_mkeys.append(key) + if del_mkeys: + commands[attr] = del_mkeys + return commands, requests + + def sort_lists_in_config(self, config): + """ Sort the lists in the config """ + if config: + config.sort(key=lambda x: x['name']) + for cfg in config: + if cfg.get('ospf_attributes', []): + cfg['ospf_attributes'].sort(key=lambda x: x['address']) + for ospf_attr in cfg.get('ospf_attributes', []): + if ospf_attr.get('md_authentication', []): + ospf_attr['md_authentication'].sort(key=lambda x: x['key_id']) + + def _strip_default_address(self, conf): + if conf: + for cfg in conf: + for ospf_attr in cfg.get('ospf_attributes', []): + if ospf_attr.get('address') == DEFAULT_ADDRESS: + del ospf_attr['address'] + + def _add_default_address(self, conf): + if conf: + for cfg in conf: + for ospf_attr in cfg.get('ospf_attributes', []): + if not ospf_attr.get('address'): + ospf_attr['address'] = DEFAULT_ADDRESS + + def _getIpv4Address(self, ip): + if ip.isdigit(): + ip = int(ip) + try: + return ipaddress.IPv4Address(ip) + except ipaddress.AddressValueError: + self._module.fail_json(msg="Invalid IPv4 address: {}".format(ip)) + + def get_ospf_intf_uri(self, intf_name, sub_intf=0): + intf_name = intf_name.replace('/', '%2f') + ospf_intf_uri = '/data/openconfig-interfaces:interfaces/interface={}'.format(intf_name) + if intf_name.startswith('Vlan'): + ospf_intf_uri += '/openconfig-vlan:routed-vlan' + else: + ospf_intf_uri += '/subinterfaces/subinterface={}'.format(sub_intf) + return ospf_intf_uri + + def get_ospf_uri(self, intf_name, sub_intf=0): + ospf_uri = self.get_ospf_intf_uri(intf_name, sub_intf) + ospf_uri += '/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + return ospf_uri + + def get_ospf_if_and_subif(self, intf_name): + return intf_name.split('.') if '.' in intf_name else (intf_name, 0) + + def update_dict(self, src, dest, src_key, dest_key, value=False): + if not value: + if src.get(src_key) is not None: + dest[dest_key] = src[src_key] + elif src: + dest.update(value) + + def _normalize_interface_name(self, want): + normalize_interface_name(want, self._module) + for conf in want: + ospf_attr = conf.get('ospf_attributes', []) + for o_attr in ospf_attr: + if 'area_id' in o_attr: + o_attr['area_id'] = str(self._getIpv4Address(o_attr['area_id'])) + return want + + def _get_generated_config(self, want, new_config, state): + if state == 'merged': + for cmd in want: + name = cmd.get('name') + match = next((cfg for cfg in new_config if cfg['name'] == name), None) + if match: + match_ospf_attr = match.get('ospf_attributes', []) + cmd_ospf_attr = cmd.get('ospf_attributes', []) + for o_attr in cmd_ospf_attr: + m_attr = next((a for a in match_ospf_attr if a.get('address') == o_attr.get('address')), None) + if m_attr: + if 'dead_interval' in o_attr: + m_attr.pop('hello_multiplier', None) + if 'hello_multiplier' in o_attr: + m_attr.pop('dead_interval', None) + return new_config + else: + new_generated_config = [] + for cmd in new_config: + cmd_ospf_attr = cmd.get('ospf_attributes', []) + ospf_attr = [] + for o_attr in cmd_ospf_attr: + if len(o_attr) > 1: + md_keys = [] + o_md_keys = o_attr.get('md_authentication', []) + if o_md_keys: + for key in o_md_keys: + if len(key) > 1: + md_keys.append(key) + if not md_keys: + o_attr.pop('md_authentication', None) + else: + o_attr['md_authentication'] = md_keys + if len(o_attr) > 1: + ospf_attr.append(o_attr) + if ospf_attr: + cmd['ospf_attributes'] = ospf_attr + else: + cmd.pop('ospf_attributes', None) + if len(cmd) > 1: + new_generated_config.append(cmd) + return new_generated_config diff --git a/plugins/module_utils/network/sonic/config/route_maps/route_maps.py b/plugins/module_utils/network/sonic/config/route_maps/route_maps.py index 380797a56..d33cfe70a 100644 --- a/plugins/module_utils/network/sonic/config/route_maps/route_maps.py +++ b/plugins/module_utils/network/sonic/config/route_maps/route_maps.py @@ -45,6 +45,10 @@ edit_config ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + convert_routemap_bgp_asn, + to_extcom_str_list +) TEST_KEYS = [ {"config": {"map_name": "", "sequence_num": ""}} @@ -163,6 +167,7 @@ def set_config(self, existing_route_maps_facts): want = self._module.params['config'] if want: want = self.validate_and_normalize_config(want) + convert_routemap_bgp_asn(want) else: want = [] @@ -580,7 +585,7 @@ def get_route_map_modify_set_attr(self, command, route_map_statement, have): if cmd_set_top.get('as_path_prepend'): route_map_bgp_actions['set-as-path-prepend'] = { 'config': { - 'openconfig-routing-policy-ext:asn-list': cmd_set_top['as_path_prepend'] + 'openconfig-routing-policy-ext:asn-list': cmd_set_top['as_path_prepend'].to_request_attr_fmt() } } @@ -701,13 +706,13 @@ def get_route_map_modify_set_attr(self, command, route_map_statement, have): route_map_bgp_actions['set-ext-community']['inline']['config']['communities'] if cmd_set_top['extcommunity'].get('rt'): - rt_list = cmd_set_top['extcommunity']['rt'] + rt_list = to_extcom_str_list(cmd_set_top['extcommunity']['rt']) for rt_val in rt_list: rmap_set_extcommunities_cfg.append("route-target:" + rt_val) if cmd_set_top['extcommunity'].get('soo'): - soo_list = cmd_set_top['extcommunity']['soo'] + soo_list = to_extcom_str_list(cmd_set_top['extcommunity']['soo']) for soo in soo_list: rmap_set_extcommunities_cfg.append("route-origin:" + soo) @@ -770,6 +775,10 @@ def get_route_map_modify_set_attr(self, command, route_map_statement, have): if cmd_set_top.get('weight'): route_map_bgp_actions_cfg['set-weight'] = cmd_set_top['weight'] + # Handle set tag + if cmd_set_top.get('tag'): + route_map_bgp_actions_cfg['set-tag'] = cmd_set_top['tag'] + @staticmethod def get_route_map_modify_call_attr(command, route_map_statement): '''In the dict specified by the input route_map_statement paramenter, @@ -1442,7 +1451,7 @@ def get_route_map_delete_set_bgp_cfg(self, command, set_both_keys, cmd_rmap_have # Note: Although 'metric' (REST API 'set-med') is in this REST API configuration # group, it is handled separately as part of deleting the top level, functionally # related 'metric-action' attribute. - bgp_cfg_keys = {'ip_next_hop', 'origin', 'local_preference', 'ipv6_next_hop', 'weight'} + bgp_cfg_keys = {'ip_next_hop', 'origin', 'local_preference', 'ipv6_next_hop', 'weight', 'tag'} delete_bgp_keys = bgp_cfg_keys.intersection(set_both_keys) if not delete_bgp_keys: for bgp_key in bgp_cfg_keys: @@ -1479,7 +1488,8 @@ def get_route_map_delete_set_bgp_cfg(self, command, set_both_keys, cmd_rmap_have 'ip_next_hop': 'set-next-hop', 'local_preference': 'set-local-pref', 'origin': 'set-route-origin', - 'weight': 'set-weight' + 'weight': 'set-weight', + 'tag': 'set-tag', } for bgp_cfg_key in bgp_cfg_rest_names: @@ -1926,6 +1936,7 @@ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have, 'metric', 'origin', 'weight', + 'tag', ] set_uri_attr = { @@ -1941,7 +1952,8 @@ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have, 'local_preference': bgp_set_delete_req_base + 'config/set-local-pref', 'metric': metric_uri, 'origin': bgp_set_delete_req_base + 'config/set-route-origin', - 'weight': bgp_set_delete_req_base + 'config/set-weight' + 'weight': bgp_set_delete_req_base + 'config/set-weight', + 'tag': bgp_set_delete_req_base + 'config/set-tag' } # Remove all appropriate "set" configuration for this route map if any of the @@ -1999,7 +2011,7 @@ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have, if cmd_set_nested: if not command.get('set'): command['set'] = {} - command['set'].update(cmd_set_nested) + command['set'].update(cmd_set_nested) if not command.get('set'): command['set'] = {} cmd_set_top = command['set'] @@ -2104,10 +2116,14 @@ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have, # extcommunity list cfg_extcommunity_list_set = set(cfg_set_top['extcommunity'][extcomm_type]) cmd_extcommunity_list_set = ([]) + saved_cmd_set = [] if cmd_set_top.get('extcommunity') and extcomm_type in cmd_set_top['extcommunity']: - cmd_extcommunity_list_set = set(cmd_set_top['extcommunity'][extcomm_type]) - command['set']['extcommunity'].pop(extcomm_type) + cmd_extcommunity_list_set = set(to_extcom_str_list(cmd_set_top['extcommunity'][extcomm_type])) + saved_cmd_set = command['set']['extcommunity'].pop(extcomm_type) for extcomm_number in cfg_extcommunity_list_set.difference(cmd_extcommunity_list_set): + if extcomm_number in saved_cmd_set: + # ignore equivalent asn:nn with different as-notation format + continue set_extcommunity_delete_attrs.append( self.set_extcomm_rest_names[extcomm_type] + extcomm_number) @@ -2268,7 +2284,7 @@ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have, if extcomm_type in cfg_set_top['extcommunity']: symmetric_diff_set = \ (set( - cmd_set_top['extcommunity'][extcomm_type]).symmetric_difference( + to_extcom_str_list(cmd_set_top['extcommunity'][extcomm_type])).symmetric_difference( set(cfg_set_top['extcommunity'][extcomm_type]))) if symmetric_diff_set: # Append eligible entries to the delete list. diff --git a/plugins/module_utils/network/sonic/config/system/system.py b/plugins/module_utils/network/sonic/config/system/system.py index fe5fac8cf..2720d1b53 100644 --- a/plugins/module_utils/network/sonic/config/system/system.py +++ b/plugins/module_utils/network/sonic/config/system/system.py @@ -18,6 +18,7 @@ ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, to_list, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( @@ -36,7 +37,6 @@ get_new_config, get_formatted_config_diff ) -from copy import deepcopy PATCH = 'patch' DELETE = 'delete' @@ -58,6 +58,11 @@ def __derive_system_config_delete_op(key_set, command, exist_conf): new_conf['anycast_address']['mac_address'] = None if 'auto_breakout' in command: new_conf['auto_breakout'] = 'DISABLE' + if 'load_share_hash_algo' in command: + new_conf['load_share_hash_algo'] = None + if 'audit_rules' in command: + new_conf['audit_rules'] = 'NONE' + return True, new_conf @@ -167,10 +172,8 @@ def set_state(self, want, have): elif state == 'merged': diff = get_diff(want, have) commands = self._state_merged(want, have, diff) - elif state == 'overridden': - commands = self._state_overridden(want, have) - elif state == 'replaced': - commands = self._state_replaced(want, have) + elif state in ('overridden', 'replaced'): + commands = self._state_replaced_overridden(want, have) return commands @@ -216,8 +219,8 @@ def _state_deleted(self, want, have): return commands, requests - def _state_replaced(self, want, have): - """ The command generator when state is replaced + def _state_replaced_overridden(self, want, have): + """ The command generator when state is replaced or overridden :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary @@ -227,73 +230,68 @@ def _state_replaced(self, want, have): to the desired configuration """ commands = [] - requests = [] - merged_commands = [] - merged_requests = [] + add_command = {} + del_command = {} - new_want = self.patch_want_with_default(want, ac_address_only=True) - replaced_config = self.get_replaced_config(have, new_want) - if replaced_config: - requests = self.get_delete_all_system_request(replaced_config) - if len(requests) > 0: - commands = update_states(replaced_config, "deleted") - if 'hostname' in commands or 'interface_naming' in commands: - # All existing config is to be deleted. - merged_commands = new_want - else: - # Only the "anycast_address" config is to be deleted. - new_have = deepcopy(have) - new_have.pop('anycast_address', None) - merged_commands = get_diff(new_want, new_have) - - if merged_commands: - merged_requests = self.get_create_system_request(have, merged_commands) - - if len(merged_requests) > 0: - merged_commands = update_states(merged_commands, "replaced") + requests = [] + del_requests = [] + + default_values = { + 'hostname': 'sonic', + 'interface_naming': 'native', + 'anycast_address': { + 'ipv4': True, + 'ipv6': True + }, + 'auto_breakout': 'DISABLE' + } + del_request_method = { + 'hostname': self.get_hostname_delete_request, + 'interface_naming': self.get_intfname_delete_request, + 'auto_breakout': self.get_auto_breakout_delete_request, + 'load_share_hash_algo': self.get_load_share_hash_algo_delete_request, + 'audit_rules': self.get_audit_rules_delete_request + } + + new_have = remove_empties(have) + new_want = remove_empties(want) + + for option in ('hostname', 'interface_naming', 'auto_breakout', 'load_share_hash_algo', 'audit_rules'): + if option in new_want: + if new_want[option] != new_have.get(option): + add_command[option] = new_want[option] else: - merged_commands = [] - merged_requests = [] - - commands.extend(merged_commands) - requests.extend(merged_requests) - - return commands, requests - - def _state_overridden(self, want, have): - """ The command generator when state is overridden + if option in new_have and new_have[option] != default_values.get(option): + del_command[option] = new_have[option] + del_requests.append(del_request_method[option]()) + + want_anycast = new_want.get('anycast_address', {}) + have_anycast = new_have.get('anycast_address', {}) + if want_anycast: + for option in ('ipv4', 'ipv6', 'mac_address'): + if option in want_anycast: + if want_anycast[option] != have_anycast.get(option): + add_command.setdefault('anycast_address', {}) + add_command['anycast_address'][option] = want_anycast[option] + else: + if option in have_anycast and have_anycast[option] != default_values['anycast_address'].get(option): + del_command.setdefault('anycast_address', {}) + del_command['anycast_address'][option] = have_anycast[option] - :param want: the desired configuration as a dictionary - :param have: the current configuration as a dictionary - :param diff: the difference between want and have - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ - commands = [] - requests = [] - merged_requests = [] - merged_commands = [] + if del_command.get('anycast_address'): + del_requests.extend(self.get_anycast_delete_request(del_command['anycast_address'])) + else: + if have_anycast: + del_command['anycast_address'] = have_anycast + del_requests.extend(self.get_anycast_delete_request(del_command['anycast_address'])) - new_want = self.patch_want_with_default(want) - if have and have != new_want: - requests = self.get_delete_all_system_request(have) - if len(requests) > 0: - commands = update_states(have, "deleted") - else: - requests = [] - have = [] - - if not have and new_want: - merged_commands = deepcopy(new_want) - merged_requests = self.get_create_system_request(have, merged_commands) - if len(merged_requests) > 0: - merged_commands = update_states(merged_commands, "overridden") - else: - merged_commands = [] + if del_command: + commands = update_states(del_command, 'deleted') + requests.extend(del_requests) - commands.extend(merged_commands) - requests.extend(merged_requests) + if add_command: + commands.extend(update_states(add_command, self._module.params['state'])) + requests.extend(self.get_create_system_request(new_want, add_command)) return commands, requests @@ -320,6 +318,16 @@ def get_create_system_request(self, want, commands): if auto_breakout_payload: request = {'path': auto_breakout_path, 'method': method, 'data': auto_breakout_payload} requests.append(request) + load_share_hash_algo_path = "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + load_share_hash_algo_payload = self.build_create_load_share_hash_algo_payload(commands) + if load_share_hash_algo_payload: + request = {'path': load_share_hash_algo_path, 'method': method, 'data': load_share_hash_algo_payload} + requests.append(request) + audit_rules_path = 'data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules' + audit_rules_payload = self.build_create_audit_rules_payload(commands) + if audit_rules_payload: + request = {'path': audit_rules_path, 'method': method, 'data': audit_rules_payload} + requests.append(request) return requests def build_create_hostname_payload(self, commands): @@ -364,81 +372,18 @@ def build_create_auto_breakout_payload(self, commands): payload.update({'sonic-device-metadata:auto-breakout': commands["auto_breakout"]}) return payload - def patch_want_with_default(self, want, ac_address_only=False): - new_want = {} - if want is None: - if ac_address_only: - new_want = {'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None}} - else: - new_want = {'hostname': 'sonic', 'interface_naming': 'native', - 'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None}, - 'auto_breakout': 'DISABLE'} - else: - new_want = want.copy() - new_anycast = {} - anycast = want.get('anycast_address', None) - if not anycast: - new_anycast = {'ipv4': True, 'ipv6': True, 'mac_address': None} - else: - new_anycast = anycast.copy() - ipv4 = anycast.get("ipv4", None) - if ipv4 is None: - new_anycast["ipv4"] = True - ipv6 = anycast.get("ipv6", None) - if ipv6 is None: - new_anycast["ipv6"] = True - mac = anycast.get("mac_address", None) - if mac is None: - new_anycast["mac_address"] = None - new_want["anycast_address"] = new_anycast - - if not ac_address_only: - hostname = want.get('hostname', None) - if hostname is None: - new_want["hostname"] = 'sonic' - intf_name = want.get('interface_naming', None) - if intf_name is None: - new_want["interface_naming"] = 'native' - auto_breakout_mode = want.get('auto_breakout', None) - if auto_breakout_mode is None: - new_want["auto_breakout"] = 'DISABLE' - return new_want - - def get_replaced_config(self, have, want): - - replaced_config = dict() - top_level_want = False - - w_hostname = want.get('hostname', None) - w_intf_name = want.get('interface_naming', None) - w_auto_breakout_mode = want.get('auto_breakout', None) - - if w_hostname is not None or w_intf_name is not None or w_auto_breakout_mode is not None: - top_level_want = True - - if top_level_want: - h_hostname = have.get('hostname', None) - if (h_hostname != w_hostname): - replaced_config = have.copy() - return replaced_config - - h_intf_name = have.get('interface_naming', None) - if (h_intf_name != w_intf_name): - replaced_config = have.copy() - return replaced_config - - h_auto_breakout_mode = have.get('auto_breakout', None) - if (h_auto_breakout_mode != w_auto_breakout_mode) and w_auto_breakout_mode: - replaced_config = have.copy() - return replaced_config - - h_ac_addr = have.get('anycast_address', None) - w_ac_addr = want.get('anycast_address', None) - if (h_ac_addr != w_ac_addr) and w_ac_addr: - replaced_config['anycast_address'] = h_ac_addr - return replaced_config - - return replaced_config + def build_create_load_share_hash_algo_payload(self, commands): + payload = {} + if "load_share_hash_algo" in commands and commands["load_share_hash_algo"]: + payload = {"openconfig-loadshare-mode-ext:config": {}} + payload['openconfig-loadshare-mode-ext:config'].update({"algorithm": commands["load_share_hash_algo"]}) + return payload + + def build_create_audit_rules_payload(self, commands): + payload = {} + if "audit_rules" in commands and commands["audit_rules"]: + payload.update({'openconfig-system-ext:audit-rules': commands["audit_rules"]}) + return payload def remove_default_entries(self, data): new_data = {} @@ -467,6 +412,12 @@ def remove_default_entries(self, data): auto_breakout_mode = data.get('auto_breakout', None) if auto_breakout_mode != "DISABLE": new_data["auto_breakout"] = auto_breakout_mode + load_share_hash_algo = data.get('load_share_hash_algo', None) + if load_share_hash_algo is not None: + new_data["load_share_hash_algo"] = load_share_hash_algo + audit_rules = data.get('audit_rules', None) + if audit_rules is not None and audit_rules != "NONE": + new_data["audit_rules"] = audit_rules return new_data def get_delete_all_system_request(self, have): @@ -483,10 +434,17 @@ def get_delete_all_system_request(self, have): if "auto_breakout" in have: request = self.get_auto_breakout_delete_request() requests.append(request) + if "load_share_hash_algo" in have: + request = self.get_load_share_hash_algo_delete_request() + requests.append(request) + if "audit_rules" in have: + request = self.get_audit_rules_delete_request() + requests.append(request) + return requests def get_hostname_delete_request(self): - path = 'data/openconfig-system:system/config/' + path = 'data/openconfig-system:system/config' method = PATCH payload = {"openconfig-system:config": {}} payload['openconfig-system:config'].update({"hostname": "sonic"}) @@ -523,3 +481,15 @@ def get_auto_breakout_delete_request(self): method = DELETE request = {'path': path, 'method': method} return request + + def get_load_share_hash_algo_delete_request(self): + path = 'data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config/algorithm' + method = DELETE + request = {'path': path, 'method': method} + return request + + def get_audit_rules_delete_request(self): + path = 'data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules' + method = DELETE + request = {'path': path, 'method': method} + return request diff --git a/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py b/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py index acb72db17..b9a64fec4 100644 --- a/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py +++ b/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py @@ -35,8 +35,13 @@ TEST_KEYS = [ {'config': {'name': ''}}, - {'mapping': {'service_vlan': '', 'dot1q_tunnel': ''}}, + {'mapping': {'service_vlan': ''}}, + {'match_single_tags': {'outer_vlan': ''}}, + {'match_double_tags': {'inner_vlan': '', 'outer_vlan': ''}}, ] +interface_url = "data/openconfig-interfaces:interfaces/interface={}" +mapped_vlans_url = interface_url + "/openconfig-interfaces-ext:mapped-vlans" +mapped_vlan_url = mapped_vlans_url + "/mapped-vlan={}" class Vlan_mapping(ConfigBase): @@ -79,6 +84,7 @@ def execute_module(self): existing_vlan_mapping_facts = self.get_vlan_mapping_facts() commands, requests = self.set_config(existing_vlan_mapping_facts) + if commands: if not self._module.check_mode: try: @@ -120,6 +126,7 @@ def set_state(self, want, have): to the desired configuration """ state = self._module.params['state'] + have = self.convert_vlan_ids_range(have) want = self.convert_vlan_ids_range(want) diff = get_diff(want, have, TEST_KEYS) @@ -143,26 +150,26 @@ def _state_replaced(self, want, have, diff): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = [] commands = [] - commands_del = [] - - commands_del = self.get_replaced_delete_list(want, have) - - if commands_del: - commands.extend(update_states(commands_del, "deleted")) + requests = [] + replaced_config = self.get_replaced_config(want, have) + + add_commands = [] + if replaced_config: + del_requests = self.get_delete_vlan_mapping_requests(replaced_config, have, + is_delete_all=False, + state='replaced') + requests.extend(del_requests) + commands.extend(update_states(replaced_config, "deleted")) + add_commands = want + else: + add_commands = diff - requests_del = self.get_delete_vlan_mapping_requests(commands_del, have, is_delete_all=True) - if requests_del: - requests.extend(requests_del) - - if diff or commands_del: - requests_rep = self.get_create_vlan_mapping_requests(want, have) - if len(requests_rep): - requests.extend(requests_rep) - commands = update_states(want, "replaced") - else: - commands = [] + if add_commands: + add_requests = self.get_create_vlan_mapping_requests(add_commands, have) + if len(add_requests) > 0: + requests.extend(add_requests) + commands.extend(update_states(add_commands, "replaced")) return commands, requests @@ -176,19 +183,19 @@ def _state_overridden(self, want, have, diff): commands = [] requests = [] - commands_del = get_diff(have, want, TEST_KEYS) - if commands_del: - requests_del = self.get_delete_vlan_mapping_requests(commands_del, have, is_delete_all=True) - requests.extend(requests_del) - commands_del = update_states(commands_del, "deleted") - commands.extend(commands_del) + r_diff = get_diff(have, want, TEST_KEYS) + if have and (diff or r_diff): + del_requests = self.get_delete_vlan_mapping_requests(have, have, is_delete_all=True) + requests.extend(del_requests) + commands.extend(update_states(have, "deleted")) + have = [] - commands_over = diff - if diff: - requests_over = self.get_create_vlan_mapping_requests(commands_over, have) - requests.extend(requests_over) - commands_over = update_states(commands_over, "overridden") - commands.extend(commands_over) + if not have and want: + want_commands = want + want_requests = self.get_create_vlan_mapping_requests(want_commands, have) + if len(want_requests) > 0: + requests.extend(want_requests) + commands.extend(update_states(want_commands, "overridden")) return commands, requests @@ -236,64 +243,11 @@ def _state_deleted(self, want, have): return commands, requests - def get_replaced_delete_list(self, commands, have): - matched = [] - - for cmd in commands: - name = cmd.get('name', None) - interface_name = name.replace('/', '%2f') - mapping_list = cmd.get('mapping', []) - - matched_interface_name = None - matched_mapping_list = [] - for existing in have: - have_name = existing.get('name', None) - have_interface_name = have_name.replace('/', '%2f') - have_mapping_list = existing.get('mapping', []) - if interface_name == have_interface_name: - matched_interface_name = have_interface_name - matched_mapping_list = have_mapping_list - - if mapping_list and matched_mapping_list: - returned_mapping_list = [] - for mapping in mapping_list: - service_vlan = mapping.get('service_vlan', None) - - for matched_mapping in matched_mapping_list: - matched_service_vlan = matched_mapping.get('service_vlan', None) - - if matched_service_vlan and service_vlan: - if matched_service_vlan == service_vlan: - priority = mapping.get('priority', None) - have_priority = matched_mapping.get('priority', None) - inner_vlan = mapping.get('inner_vlan', None) - have_inner_vlan = matched_mapping.get('inner_vlan', None) - dot1q_tunnel = mapping.get('dot1q_tunnel', False) - have_dot1q_tunnel = matched_mapping.get('dot1q_tunnel', False) - vlan_ids = mapping.get('vlan_ids', []) - have_vlan_ids = matched_mapping.get('vlan_ids', []) - - if priority != have_priority: - returned_mapping_list.append(mapping) - elif inner_vlan != have_inner_vlan: - returned_mapping_list.append(mapping) - elif dot1q_tunnel != have_dot1q_tunnel: - returned_mapping_list.append(mapping) - elif sorted(vlan_ids) != sorted(have_vlan_ids): - returned_mapping_list.append(mapping) - - if returned_mapping_list: - matched.append({'name': interface_name, 'mapping': returned_mapping_list}) - - return matched - - def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all): + def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all, state='deleted'): """ Get list of requests to delete vlan mapping configurations for all interfaces specified by the commands """ - url = "data/openconfig-interfaces:interfaces/interface={}/openconfig-interfaces-ext:mapped-vlans/mapped-vlan={}" - priority_url = "/ingress-mapping/config/mapped-vlan-priority" - vlan_ids_url = "/match/single-tagged/config/vlan-ids={}" + url = mapped_vlan_url method = "DELETE" requests = [] @@ -312,7 +266,6 @@ def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all): requests.append(request) return requests - else: for cmd in commands: name = cmd.get('name', None) @@ -322,56 +275,20 @@ def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all): # Checks if there is a interface matching the delete command have_interface_name = None have_mapping_list = [] - for tmp in have: - tmp_name = tmp.get('name', None) - tmp_interface_name = tmp_name.replace('/', '%2f') - tmp_mapping_list = tmp.get('mapping', []) - if interface_name == tmp_interface_name: - have_interface_name = tmp_interface_name - have_mapping_list = tmp_mapping_list + for conf in have: + conf_name = conf.get('name', None) + conf_interface_name = conf_name.replace('/', '%2f') + conf_mapping_list = conf.get('mapping', []) + if interface_name == conf_interface_name: + have_mapping_list = conf_mapping_list + break # Delete part or all of single mapping - if mapping_list: - for mapping in mapping_list: - service_vlan = mapping.get('service_vlan', None) - vlan_ids = mapping.get('vlan_ids', None) - priority = mapping.get('priority', None) - - # Checks if there is a vlan mapping matching the delete command - have_service_vlan = None - have_vlan_ids = None - have_priority = None - for have_mapping in have_mapping_list: - if have_mapping.get('service_vlan', None) == service_vlan: - have_service_vlan = have_mapping.get('service_vlan', None) - have_vlan_ids = have_mapping.get('vlan_ids', None) - have_priority = have_mapping.get('priority', None) - - if service_vlan and have_service_vlan: - if vlan_ids or priority: - # Delete priority - if priority and have_priority: - path = url.format(interface_name, service_vlan) + priority_url - request = {"path": path, "method": method} - requests.append(request) - # Delete vlan ids - if vlan_ids and have_vlan_ids: - vlan_ids_str = "" - same_vlan_ids_list = self.get_vlan_ids_diff(vlan_ids, have_vlan_ids, same=True) - if same_vlan_ids_list: - for vlan in same_vlan_ids_list: - if vlan_ids_str: - vlan_ids_str = vlan_ids_str + "%2C" + vlan.replace("-", "..") - else: - vlan_ids_str = vlan.replace("-", "..") - path = url.format(interface_name, service_vlan) + vlan_ids_url.format(vlan_ids_str) - request = {"path": path, "method": method} - requests.append(request) - # Delete entire mapping - else: - path = url.format(interface_name, service_vlan) - request = {"path": path, "method": method} - requests.append(request) + if mapping_list and have_mapping_list: + requests.extend(self.get_delete_vlan_mapping_mapping_requests(interface_name, + mapping_list, + have_mapping_list, + state)) # Delete all mappings in an interface else: if have_mapping_list: @@ -381,7 +298,239 @@ def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all): request = {"path": path, "method": method} requests.append(request) - return requests + return requests + + def get_delete_vlan_mapping_mapping_requests(self, interface_name, mapping_list, + have_mapping_list, state='deleted'): + url = mapped_vlan_url + method = "DELETE" + requests = [] + + if mapping_list: + for mapping in mapping_list: + service_vlan = mapping.get('service_vlan', None) + + # Checks if there is a vlan mapping matching the delete command + have_service_vlan = None + for have_mapping in have_mapping_list: + tmp_service_vlan = have_mapping.get('service_vlan', None) + if tmp_service_vlan == service_vlan: + have_service_vlan = tmp_service_vlan + break + + if service_vlan and have_service_vlan: + dot1q_tun = mapping.get('dot1q_tunnel', None) + vlan_trans = mapping.get('vlan_translation', None) + have_dot1q_tun = have_mapping.get('dot1q_tunnel', None) + have_vlan_trans = have_mapping.get('vlan_translation', None) + + if dot1q_tun is not None and have_dot1q_tun: + # Delete dot1q_tunnel + dt_requests = self.get_delete_vlan_mapping_mapping_dot1q_tunnel_requests( + interface_name, service_vlan, dot1q_tun, have_dot1q_tun) + if dt_requests: + requests.extend(dt_requests) + + if vlan_trans is not None and have_vlan_trans: + # Delete vlan translation + vt_requests = self.get_delete_vlan_mapping_mapping_translation_requests( + interface_name, service_vlan, vlan_trans, have_vlan_trans, state) + if vt_requests: + requests.extend(vt_requests) + + if dot1q_tun is None and vlan_trans is None: + if have_dot1q_tun or have_vlan_trans: + # Delete entire mapping + path = url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + return requests + + def get_delete_vlan_mapping_mapping_dot1q_tunnel_requests(self, + interface_name, service_vlan, + dot1q_tun, have_dot1q_tun): + dot1q_tun_url = mapped_vlan_url + "/match/single-tagged" + priority_url = mapped_vlan_url + "/ingress-mapping/config/mapped-vlan-priority" + vlan_ids_url = mapped_vlan_url + "/match/single-tagged/config/vlan-ids={}" + method = "DELETE" + requests = [] + + vlan_ids = dot1q_tun.get('vlan_ids', None) + priority = dot1q_tun.get('priority', None) + have_vlan_ids = have_dot1q_tun.get('vlan_ids', None) + have_priority = have_dot1q_tun.get('priority', None) + + if vlan_ids or priority: + # Delete priority + if priority and have_priority: + path = priority_url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + # Delete vlan ids + if vlan_ids and have_vlan_ids: + vlan_ids_str = "" + same_vlan_ids_list = self.get_vlan_ids_diff(vlan_ids, have_vlan_ids, same=True) + if same_vlan_ids_list: + for vlan in same_vlan_ids_list: + if vlan_ids_str: + vlan_ids_str = vlan_ids_str + "%2C" + vlan + else: + vlan_ids_str = vlan + path = vlan_ids_url.format(interface_name, service_vlan, vlan_ids_str) + request = {"path": path, "method": method} + requests.append(request) + # Delete entire dot1q_tunnel + else: + if have_vlan_ids or have_priority: + path = dot1q_tun_url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + return requests + + def get_delete_vlan_mapping_mapping_translation_requests(self, + interface_name, service_vlan, + vlan_trans, have_vlan_trans, + state='deleted'): + url = mapped_vlan_url + vlan_trans_ms_url = mapped_vlan_url + "/match/match-single-tags" + vlan_trans_md_url = mapped_vlan_url + "/match/match-double-tags" + method = "DELETE" + requests = [] + + multi_tag = vlan_trans.get('multi_tag', None) + ms_tags = vlan_trans.get('match_single_tags', None) + md_tags = vlan_trans.get('match_double_tags', None) + + have_multi_tag = have_vlan_trans.get('multi_tag', False) + + have_ms_tags = have_vlan_trans.get('match_single_tags', None) + have_md_tags = have_vlan_trans.get('match_double_tags', None) + + if multi_tag is not None: + if (state == 'replaced' and multi_tag != have_multi_tag) or \ + (state == 'deleted' and have_multi_tag): + # Delete entire translation + path = url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + return requests + + if ms_tags is not None and have_ms_tags: + # Delete match_single_tags + ms_requests = self.get_delete_vlan_mapping_mapping_ms_tags_requests( + interface_name, service_vlan, ms_tags, have_ms_tags) + if ms_requests: + requests.extend(ms_requests) + + if md_tags is not None and have_md_tags: + # Delete match_double_tags + md_requests = self.get_delete_vlan_mapping_mapping_md_tags_requests( + interface_name, service_vlan, md_tags, have_md_tags) + if md_requests: + requests.extend(md_requests) + + if ms_tags is None and md_tags is None and multi_tag is None: + if have_ms_tags or have_md_tags or have_multi_tag is not None: + # Delete entire translation + path = url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + return requests + + def get_delete_vlan_mapping_mapping_ms_tags_requests(self, + interface_name, service_vlan, + ms_tags, have_ms_tags): + ms_tags_url = mapped_vlan_url + "/match/match-single-tags" + ms_tag_url = mapped_vlan_url + "/match/match-single-tags/match-single-tag={}" + ms_tag_priority_url = ms_tag_url + "/config/priority" + method = "DELETE" + requests = [] + + if have_ms_tags: + if ms_tags: + for ms_tag in ms_tags: + outer_vlan = ms_tag.get('outer_vlan', None) + have_outer_vlan = None + for have_ms_tag in have_ms_tags: + tmp_vlan = have_ms_tag.get('outer_vlan', None) + if outer_vlan == tmp_vlan: + have_outer_vlan = tmp_vlan + break + + if outer_vlan and have_outer_vlan: + priority = ms_tag.get('priority', None) + have_priority = have_ms_tag.get('priority', None) + if priority: + if have_priority: + # Delete priority + path = ms_tag_priority_url.format(interface_name, service_vlan, + outer_vlan) + request = {"path": path, "method": method} + requests.append(request) + else: + # Delete entire tag + path = ms_tag_url.format(interface_name, service_vlan, outer_vlan) + request = {"path": path, "method": method} + requests.append(request) + else: + # Delete entire match-single-tags + path = ms_tags_url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + return requests + + def get_delete_vlan_mapping_mapping_md_tags_requests(self, + interface_name, service_vlan, + md_tags, have_md_tags): + md_tags_url = mapped_vlan_url + "/match/match-double-tags" + md_tag_url = mapped_vlan_url + "/match/match-double-tags/match-double-tag={},{}" + md_tag_priority_url = md_tag_url + "/config/priority" + method = "DELETE" + requests = [] + + if have_md_tags: + if md_tags: + for md_tag in md_tags: + inner_vlan = md_tag.get('inner_vlan', None) + outer_vlan = md_tag.get('outer_vlan', None) + have_inner_vlan = None + have_outer_vlan = None + for have_md_tag in have_md_tags: + tmp_inner_vlan = have_md_tag.get('inner_vlan', None) + tmp_outer_vlan = have_md_tag.get('outer_vlan', None) + if inner_vlan == tmp_inner_vlan and outer_vlan == tmp_outer_vlan: + have_inner_vlan = tmp_inner_vlan + have_outer_vlan = tmp_outer_vlan + break + + if inner_vlan and have_inner_vlan and outer_vlan and have_outer_vlan: + priority = md_tag.get('priority', None) + have_priority = have_md_tag.get('priority', None) + if priority: + if have_priority: + # Delete priority + path = md_tag_priority_url.format(interface_name, service_vlan, + outer_vlan, inner_vlan) + request = {"path": path, "method": method} + requests.append(request) + else: + # Delete entire tag + path = md_tag_url.format(interface_name, service_vlan, + outer_vlan, inner_vlan) + request = {"path": path, "method": method} + requests.append(request) + else: + # Delete entire match-double-tags + path = md_tags_url.format(interface_name, service_vlan) + request = {"path": path, "method": method} + requests.append(request) + + return requests def get_create_vlan_mapping_requests(self, commands, have): """ Get list of requests to create/modify vlan mapping configurations @@ -398,70 +547,143 @@ def get_create_vlan_mapping_requests(self, commands, have): if mapping_list: for mapping in mapping_list: - requests.append(self.get_create_vlan_mapping_request(interface_name, mapping)) + request = self.get_create_vlan_mapping_mapping_requests(interface_name, + mapping) + if request: + requests.append(request) return requests - def get_create_vlan_mapping_request(self, interface_name, mapping): - url = "data/openconfig-interfaces:interfaces/interface={}/openconfig-interfaces-ext:mapped-vlans" + def get_create_vlan_mapping_mapping_requests(self, interface_name, mapping): + url = mapped_vlans_url body = {} method = "PATCH" - match_data = None service_vlan = mapping.get('service_vlan', None) - priority = mapping.get('priority', None) - vlan_ids = mapping.get('vlan_ids', []) - dot1q_tunnel = mapping.get('dot1q_tunnel', None) - inner_vlan = mapping.get('inner_vlan', None) - - if not dot1q_tunnel: - if len(vlan_ids) > 1: - raise Exception("When dot1q-tunnel is false only one VLAN ID can be passed to the vlan_ids list") - if not vlan_ids and priority: - match_data = None - elif vlan_ids: - if inner_vlan: - match_data = {'double-tagged': {'config': {'inner-vlan-id': inner_vlan, 'outer-vlan-id': int(vlan_ids[0])}}} - else: - match_data = {'single-tagged': {'config': {'vlan-ids': [int(vlan_ids[0])]}}} - if priority: - ing_data = {'config': {'vlan-stack-action': 'SWAP', 'mapped-vlan-priority': priority}} - egr_data = {'config': {'vlan-stack-action': 'SWAP', 'mapped-vlan-priority': priority}} - else: - ing_data = {'config': {'vlan-stack-action': 'SWAP'}} - egr_data = {'config': {'vlan-stack-action': 'SWAP'}} - else: - if inner_vlan: - raise Exception("Inner vlan can only be passed when dot1q_tunnel is false") - if not vlan_ids and priority: - match_data = None - elif vlan_ids: - vlan_ids_list = [] - for vlan in vlan_ids: - vlan_ids_list.append(int(vlan)) - match_data = {'single-tagged': {'config': {'vlan-ids': vlan_ids_list}}} + match_data = dict() + ing_data = dict() + egr_data = dict() + request = dict() + + if 'dot1q_tunnel' in mapping: + dot1q = mapping['dot1q_tunnel'] + vlan_ids = dot1q.get('vlan_ids', []) + priority = dot1q.get('priority', None) + if vlan_ids: + match_data = {'single-tagged': {'config': {'vlan-ids': self.get_vlan_int_list(vlan_ids)}}} if priority: ing_data = {'config': {'vlan-stack-action': 'PUSH', 'mapped-vlan-priority': priority}} egr_data = {'config': {'vlan-stack-action': 'POP', 'mapped-vlan-priority': priority}} else: ing_data = {'config': {'vlan-stack-action': 'PUSH'}} egr_data = {'config': {'vlan-stack-action': 'POP'}} - if match_data: - body = {'openconfig-interfaces-ext:mapped-vlans': {'mapped-vlan': [ - {'vlan-id': service_vlan, - 'config': {'vlan-id': service_vlan}, - 'match': match_data, - 'ingress-mapping': ing_data, - 'egress-mapping': egr_data} - ]}} - else: - body = {'openconfig-interfaces-ext:mapped-vlans': {'mapped-vlan': [ - {'vlan-id': service_vlan, - 'config': {'vlan-id': service_vlan}, - 'ingress-mapping': ing_data, - 'egress-mapping': egr_data} - ]}} - - request = {"path": url.format(interface_name), "method": method, "data": body} + + if match_data: + body = { + 'openconfig-interfaces-ext:mapped-vlans': { + 'mapped-vlan': [ + { + 'vlan-id': service_vlan, + 'config': {'vlan-id': service_vlan}, + 'match': match_data, + 'ingress-mapping': ing_data, + 'egress-mapping': egr_data + } + ] + } + } + else: + body = { + 'openconfig-interfaces-ext:mapped-vlans': { + 'mapped-vlan': [ + { + 'vlan-id': service_vlan, + 'config': {'vlan-id': service_vlan}, + 'ingress-mapping': ing_data, + 'egress-mapping': egr_data + } + ] + } + } + + elif 'vlan_translation' in mapping: + multi_tag = mapping['vlan_translation'].get('multi_tag', False) + ms_tags = mapping['vlan_translation'].get('match_single_tags', None) + md_tags = mapping['vlan_translation'].get('match_double_tags', None) + + if ms_tags: + m_s_tags = [] + for ms_tag in ms_tags: + outer_vlan = ms_tag.get('outer_vlan', None) + priority = ms_tag.get('priority', None) + if priority: + m_s_tag = {'outer-vlan': outer_vlan, + 'config': {'outer-vlan': outer_vlan, + 'priority': priority}} + else: + m_s_tag = {'outer-vlan': outer_vlan, + 'config': {'outer-vlan': outer_vlan}} + m_s_tags.append(m_s_tag) + + if m_s_tags: + match_data['match-single-tags'] = {'match-single-tag': m_s_tags} + + if md_tags: + m_d_tags = [] + for md_tag in md_tags: + inner_vlan = md_tag.get('inner_vlan', None) + outer_vlan = md_tag.get('outer_vlan', None) + priority = md_tag.get('priority', None) + if priority: + m_d_tag = {'inner-vlan': inner_vlan, + 'outer-vlan': outer_vlan, + 'config': {'inner-vlan': inner_vlan, + 'outer-vlan': outer_vlan, + 'priority': priority}} + else: + m_d_tag = {'inner-vlan': inner_vlan, + 'outer-vlan': outer_vlan, + 'config': {'inner-vlan': inner_vlan, + 'outer-vlan': outer_vlan}} + m_d_tags.append(m_d_tag) + + if m_d_tags: + match_data['match-double-tags'] = {'match-double-tag': m_d_tags} + + ing_data = {'config': {'vlan-stack-action': 'SWAP'}} + egr_data = {'config': {'vlan-stack-action': 'SWAP'}} + + if match_data: + body = { + 'openconfig-interfaces-ext:mapped-vlans': { + 'mapped-vlan': [ + { + 'vlan-id': service_vlan, + 'config': {'vlan-id': service_vlan, + 'multi-tag': multi_tag}, + 'match': match_data, + 'ingress-mapping': ing_data, + 'egress-mapping': egr_data + } + ] + } + } + else: + body = { + 'openconfig-interfaces-ext:mapped-vlans': { + 'mapped-vlan': [ + { + 'vlan-id': service_vlan, + 'config': {'vlan-id': service_vlan, + 'multi-tag': multi_tag}, + 'ingress-mapping': ing_data, + 'egress-mapping': egr_data + } + ] + } + } + + if body: + request = {"path": url.format(interface_name), "method": method, "data": body} return request def get_vlan_ids_diff(self, vlan_ids, have_vlan_ids, same): @@ -504,14 +726,199 @@ def convert_vlan_ids_range(self, config): interface_name = name.replace('/', '%2f') mapping_list = conf.get('mapping', []) - mapping_index = 0 if mapping_list: + mapping_index = 0 for mapping in mapping_list: - vlan_ids = mapping.get('vlan_ids', None) + if mapping.get('dot1q_tunnel', None): + vlan_ids = mapping['dot1q_tunnel'].get('vlan_ids', None) - if vlan_ids: - config[interface_index]['mapping'][mapping_index]['vlan_ids'] = self.vlanIdsRangeStr(vlan_ids) + if vlan_ids: + dot1q_tun = config[interface_index]['mapping'][mapping_index]['dot1q_tunnel'] + dot1q_tun['vlan_ids'] = self.vlanIdsRangeStr(vlan_ids) mapping_index = mapping_index + 1 interface_index = interface_index + 1 return config + + def get_vlan_int_list(self, str_list): + int_list = [] + for str_vid in str_list: + int_list.append(int(str_vid)) + return int_list + + def get_replaced_config(self, want, have): + rpld_config = [] + for cmd in want: + name = cmd.get('name', None) + interface_name = name.replace('/', '%2f') + mapping_list = cmd.get('mapping', []) + + have_interface_name = None + have_mapping_list = [] + for conf in have: + conf_name = conf.get('name', None) + conf_interface_name = conf_name.replace('/', '%2f') + conf_mapping_list = conf.get('mapping', []) + if interface_name == conf_interface_name: + have_mapping_list = conf_mapping_list + break + + rpld_mapping_list = None + if have_mapping_list: + if mapping_list: + rpld_mapping_list = self.get_replaced_vlan_mapping_mapping( + mapping_list, have_mapping_list) + else: + rpld_mapping_list = None + + if rpld_mapping_list is not None: + rpld_config.append({'name': interface_name, 'mapping': rpld_mapping_list}) + + return rpld_config + + def get_replaced_vlan_mapping_mapping(self, mapping_list, have_mapping_list): + rpld_mapping_list = [] + + for mapping in mapping_list: + service_vlan = mapping.get('service_vlan', None) + have_service_vlan = None + for have_mapping in have_mapping_list: + tmp_service_vlan = have_mapping.get('service_vlan', None) + if tmp_service_vlan == service_vlan: + have_service_vlan = tmp_service_vlan + break + + rpld_mapping = {} + if service_vlan and have_service_vlan: + dot1q_tun = mapping.get('dot1q_tunnel', None) + vlan_trans = mapping.get('vlan_translation', None) + have_dot1q_tun = have_mapping.get('dot1q_tunnel', None) + have_vlan_trans = have_mapping.get('vlan_translation', None) + + if dot1q_tun and have_dot1q_tun: + if self.diff_dot1q_tunnel(dot1q_tun, have_dot1q_tun): + rpld_mapping = {'service_vlan': service_vlan, + 'dot1q_tunnel': {}} + + if vlan_trans and have_vlan_trans: + rpld_trans = self.get_replaced_vlan_mapping_mapping_translation( + vlan_trans, have_vlan_trans) + if rpld_trans is not None: + rpld_mapping = {'service_vlan': service_vlan, + 'vlan_translation': rpld_trans} + if rpld_mapping: + rpld_mapping_list.append(rpld_mapping) + + if not rpld_mapping_list: + return None + else: + return rpld_mapping_list + + def get_replaced_vlan_mapping_mapping_translation(self, vlan_trans, have_vlan_trans): + rpld_trans = {} + + multi_tag = vlan_trans.get('multi_tag', None) + ms_tags = vlan_trans.get('match_single_tags', None) + md_tags = vlan_trans.get('match_double_tags', None) + + have_multi_tag = have_vlan_trans.get('multi_tag', False) + have_ms_tags = have_vlan_trans.get('match_single_tags', None) + have_md_tags = have_vlan_trans.get('match_double_tags', None) + + if multi_tag is not None: + if have_multi_tag != multi_tag: + rpld_trans['multi_tag'] = multi_tag + return rpld_trans + + if ms_tags and have_ms_tags: + if self.diff_ms_tags(ms_tags, have_ms_tags): + rpld_trans['match_single_tags'] = [] + + if md_tags and have_md_tags: + if self.diff_md_tags(md_tags, have_md_tags): + rpld_trans['match_double_tags'] = [] + + if not rpld_trans: + return None + else: + return rpld_trans + + def diff_dot1q_tunnel(self, dot1q_tun, have_dot1q_tun): + vlan_ids = dot1q_tun.get('vlan_ids', None) + priority = dot1q_tun.get('priority', None) + have_vlan_ids = have_dot1q_tun.get('vlan_ids', None) + have_priority = have_dot1q_tun.get('priority', None) + if vlan_ids: + vlan_ids = sorted(vlan_ids) + if have_vlan_ids: + have_vlan_ids = sorted(have_vlan_ids) + if priority != have_priority or vlan_ids != have_vlan_ids: + return True + else: + return False + + def diff_ms_tags(self, ms_tags, have_ms_tags, rev_way=False): + + if len(ms_tags) != len(have_ms_tags): + return True + + ms_diff = False + + for ms_tag in ms_tags: + outer_vlan = ms_tag.get('outer_vlan', None) + in_it = False + for h_ms_tag in have_ms_tags: + h_outer_vlan = h_ms_tag.get('outer_vlan', None) + if outer_vlan == h_outer_vlan: + in_it = True + break + + if in_it: + priority = ms_tag.get('priority', None) + h_priority = h_ms_tag.get('priority', None) + if priority != h_priority: + ms_diff = True + else: + ms_diff = True + + if ms_diff: + return ms_diff + + if not rev_way: + ms_diff = self.diff_ms_tags(have_ms_tags, ms_tags, rev_way=True) + + return ms_diff + + def diff_md_tags(self, md_tags, have_md_tags, rev_way=False): + + if len(md_tags) != len(have_md_tags): + return True + + md_diff = False + + for md_tag in md_tags: + outer_vlan = md_tag.get('outer_vlan', None) + inner_vlan = md_tag.get('inner_vlan', None) + in_it = False + for h_md_tag in have_md_tags: + h_outer_vlan = h_md_tag.get('outer_vlan', None) + h_inner_vlan = h_md_tag.get('inner_vlan', None) + if outer_vlan == h_outer_vlan and inner_vlan == h_inner_vlan: + in_it = True + break + + if in_it: + priority = md_tag.get('priority', None) + h_priority = h_md_tag.get('priority', None) + if priority != h_priority: + md_diff = True + else: + md_diff = True + + if md_diff: + return md_diff + + if not rev_way: + md_diff = self.diff_md_tags(have_md_tags, md_tags, rev_way=True) + + return md_diff diff --git a/plugins/module_utils/network/sonic/config/vrrp/__init__.py b/plugins/module_utils/network/sonic/config/vrrp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/vrrp/vrrp.py b/plugins/module_utils/network/sonic/config/vrrp/vrrp.py new file mode 100644 index 000000000..42ac592c2 --- /dev/null +++ b/plugins/module_utils/network/sonic/config/vrrp/vrrp.py @@ -0,0 +1,722 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_vrrp class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + update_states, + get_diff, + remove_matching_defaults, + normalize_interface_name, + get_normalize_interface_name, + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + get_new_config, + get_formatted_config_diff, + __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF, + __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF +) +from ansible.module_utils.connection import ConnectionError + +PATCH = 'patch' +DELETE = 'delete' + +IPV4_PATH = '/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/' +IPV6_PATH = '/openconfig-if-ip:ipv6/addresses/address=1::1/' + +TEST_KEYS = [ + {'config': {'name': ''}}, + {'group': {'virtual_router_id': '', 'afi': ''}}, + {'virtual_address': {'address': ''}}, + {'track_interface': {'interface': '', 'priority_increment': ''}} +] + +TEST_KEYS_formatted_diff = [ + {'config': {'name': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'group': {'virtual_router_id': '', 'afi': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'virtual_address': {'address': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}}, + {'track_interface': {'interface': '', '__delete_op': __DELETE_LEAFS_THEN_CONFIG_IF_NO_NON_KEY_LEAF}} +] + +DEFAULT_ENTRIES = [ + [ + {'name': 'group'}, + {'name': 'priority', 'default': 100} + ], + [ + {'name': 'group'}, + {'name': 'preempt', 'default': True} + ], + [ + {'name': 'group'}, + {'name': 'advertisement_interval', 'default': 1} + ], + [ + {'name': 'group'}, + {'name': 'version', 'default': 2} + ], + [ + {'name': 'group'}, + {'name': 'use_v2_checksum', 'default': False} + ], +] + +DEFAULT_ATTRIBUTES = { + 'priority': 100, + 'preempt': True, + 'advertisement_interval': 1, + 'version': 2, + 'use_v2_checksum': False +} + + +class Vrrp(ConfigBase): + """ + The sonic_vrrp class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'vrrp', + ] + + vrrp_path = 'data/openconfig-interfaces:interfaces/interface={intf_name}' + vrrp_vlan_path = vrrp_path + '/openconfig-vlan:routed-vlan' + vrrp_intf_path = vrrp_path + '/subinterfaces/subinterface={intf_index}' + + vrrp_config_path = { + 'virtual_router_id': 'vrrp', + 'preempt': 'vrrp/vrrp-group={vrid}/config/preempt', + 'use_v2_checksum': 'vrrp/vrrp-group={vrid}/config/openconfig-interfaces-ext:use-v2-checksum', + 'priority': 'vrrp/vrrp-group={vrid}/config/priority', + 'advertisement_interval': 'vrrp/vrrp-group={vrid}/config/advertisement-interval', + 'version': 'vrrp/vrrp-group={vrid}/config/openconfig-interfaces-ext:version', + 'virtual_address': 'vrrp/vrrp-group={vrid}/config/virtual-address', + 'track_interface': 'vrrp/vrrp-group={vrid}/openconfig-interfaces-ext:vrrp-track' + } + + vrrp_attributes = ('preempt', 'version', 'use_v2_checksum', 'priority', 'advertisement_interval', 'virtual_address', 'track_interface') + + def __init__(self, module): + super(Vrrp, self).__init__(module) + + def get_vrrp_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + vrrp_facts = facts['ansible_network_resources'].get('vrrp') + if not vrrp_facts: + return [] + return vrrp_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + existing_vrrp_facts = self.get_vrrp_facts() + commands, requests = self.set_config(existing_vrrp_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + + result['before'] = existing_vrrp_facts + old_config = existing_vrrp_facts + + if self._module.check_mode: + result.pop('after', None) + new_config = self._get_generated_config(commands, existing_vrrp_facts, self._module.params['state']) + self.sort_lists_in_config(new_config) + result['after(generated)'] = new_config + else: + changed_vrrp_facts = self.get_vrrp_facts() + new_config = changed_vrrp_facts + self.sort_lists_in_config(new_config) + if result['changed']: + result['after'] = changed_vrrp_facts + + if self._module._diff: + self.sort_lists_in_config(old_config) + self.sort_lists_in_config(new_config) + result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + + result['commands'] = commands + result['warnings'] = warnings + return result + + def set_config(self, existing_vrrp_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_vrrp_facts + if want: + want = remove_empties_from_list(want) + normalize_interface_name(want, self._module) + for config in want: + if config.get('group'): + for group in config['group']: + track_intf = group.get('track_interface', []) + for track in track_intf: + track['interface'] = get_normalize_interface_name(track['interface'], self._module) + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + requests = [] + state = self._module.params['state'] + + if state in ('overridden', 'replaced'): + commands, requests = self._state_replaced_or_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + return commands, requests + + def _state_replaced_or_overridden(self, want, have): + """ The command generator when state is replaced or overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, requests = [], [] + state = self._module.params['state'] + add_config, del_config, del_requests = self._get_replaced_overridden_config(want, have, state) + if del_config and len(del_requests) > 0: + requests.extend(del_requests) + commands.extend(update_states(del_config, 'deleted')) + + if add_config: + mod_requests = self.get_create_vrrp_requests(add_config) + if len(mod_requests) > 0: + requests.extend(mod_requests) + commands.extend(update_states(add_config, state)) + + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = remove_empties_from_list(get_diff(want, have)) + requests = self.get_create_vrrp_requests(commands) + + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + requests = [] + is_delete_all = False + if not want: + new_want = self.get_all_vrrps(have) + is_delete_all = True + else: + new_want = deepcopy(want) + for default_entry in DEFAULT_ENTRIES: + remove_matching_defaults(new_want, default_entry) + new_want = remove_empties_from_list(new_want) + + commands, requests = self.get_delete_vrrp_commands_requests(new_want, have, is_delete_all) + + if commands and len(requests) > 0: + commands = update_states(commands, 'deleted') + else: + commands = [] + + return commands, requests + + def _get_replaced_overridden_config(self, want, have, state): + add_config, del_config = [], [] + del_requests = [] + for cmd in want: + name = cmd.get('name') + groups = cmd.get('group', []) + match = next((m for m in have if m['name'] == name), None) + + if not match: + add_config.append(cmd) + else: + add_cfg, del_cfg = {}, {} + for group in groups: + vr_id = group.get('virtual_router_id') + afi = group.get('afi') + match_group = next((g for g in match['group'] if g['virtual_router_id'] == vr_id and g['afi'] == afi), None) + if not match_group: + add_cfg.setdefault('group', []).append(group) + else: + add_group, del_group = {}, {} + for attr in self.vrrp_attributes: + if attr in group: + if attr not in match_group: + add_group[attr] = group[attr] + elif group[attr] != match_group[attr]: + if attr == 'virtual_address': + add_vip, del_vip = [], [] + want_vip = set(self.get_vip_addresses(group[attr])) + match_vip = set(self.get_vip_addresses(match_group[attr])) + for vip in want_vip.difference(match_vip): + add_vip.append({'address': vip}) + for vip in match_vip.difference(want_vip): + del_vip.append({'address': vip}) + if add_vip: + add_group[attr] = add_vip + if del_vip: + del_group[attr] = del_vip + elif attr == 'track_interface': + add_track, del_track = [], [] + for track in group[attr]: + match_track = next((t for t in match_group[attr] if t['interface'] == track['interface']), None) + if not match_track: + add_track.append(track) + elif track['priority_increment'] != match_track['priority_increment']: + add_track.append(track) + del_track.append(match_track) + for match_track in match_group[attr]: + track = next((t for t in group[attr] if t['interface'] == match_track['interface']), None) + if not track: + del_track.append(match_track) + if add_track: + add_group[attr] = add_track + if del_track: + del_group[attr] = del_track + else: + add_group[attr] = group[attr] + elif attr in match_group: + if attr not in DEFAULT_ATTRIBUTES or match_group[attr] != DEFAULT_ATTRIBUTES[attr]: + del_group[attr] = match_group[attr] + if add_group: + add_group['virtual_router_id'] = vr_id + add_group['afi'] = afi + add_cfg.setdefault('group', []).append(add_group) + if del_group: + del_group['virtual_router_id'] = vr_id + del_group['afi'] = afi + del_cfg.setdefault('group', []).append(del_group) + commands, requests = self.get_delete_specific_vrrp_param_commands_requests(match_group, vr_id, del_group, name) + if commands: + del_requests.extend(requests) + if add_cfg: + add_cfg['name'] = name + add_config.append(add_cfg) + if del_cfg: + del_cfg['name'] = name + del_config.append(del_cfg) + + if state == 'overridden': + for conf in have: + name = conf['name'] + want_conf = next((w for w in want if w['name'] == name), None) + + if not want_conf: + del_config.append({'name': name}) + commands, requests = self.get_delete_all_vrrp_groups_commands_requests(conf.get('group', []), name) + if commands: + del_requests.extend(requests) + else: + del_cfg = {} + for group in conf['group']: + vr_id = group.get('virtual_router_id') + afi = group.get('afi') + want_groups = [] if want_conf.get('group') is None else want_conf['group'] + match_group = next((g for g in want_groups if g['virtual_router_id'] == vr_id and g['afi'] == afi), None) + if not match_group: + del_cfg.setdefault('group', []).append({'virtual_router_id': vr_id, 'afi': afi}) + commands, requests = self.get_delete_vrrp_group_command_request(name, vr_id, afi) + if commands: + del_requests.extend(requests) + if del_cfg: + del_cfg['name'] = name + del_config.append(del_cfg) + return add_config, del_config, del_requests + + def get_create_vrrp_requests(self, commands): + """ Get list of requests to create/modify VRRP and VRRP6 configurations + for all interfaces specified by the commands + """ + requests = [] + + if not commands: + return requests + + for cmd in commands: + name = cmd.get('name', None) + intf_name = name.replace('/', '%2f') + group_list = cmd.get('group', []) + if group_list: + for group in group_list: + virtual_router_id = group.get('virtual_router_id', None) + if 'Vlan' in intf_name: + keypath = self.vrrp_vlan_path.format(intf_name=intf_name) + else: + parent_intf, sub_intf = intf_name.split('.') if '.' in intf_name else (intf_name, 0) + keypath = self.vrrp_intf_path.format(intf_name=parent_intf, intf_index=sub_intf) + requests.extend(self.get_create_specific_vrrp_param_requests(virtual_router_id, group, keypath)) + + return requests + + def get_create_specific_vrrp_param_requests(self, virtual_router_id, group, keypath): + """ Get list of requests to create/modify VRRP and VRRP6 configurations + based on the command specified for the interface + """ + requests = [] + + afi = group.get('afi') + ip_path = IPV4_PATH if afi == 'ipv4' else IPV6_PATH + vip_addresses = self.get_vip_addresses(group.get('virtual_address')) + preempt = group.get('preempt') + advertisement_interval = group.get('advertisement_interval') + priority = group.get('priority') + version = group.get('version') + use_v2_checksum = group.get('use_v2_checksum') + track_interfaces = self.get_track_interfaces(group.get('track_interface')) + if not virtual_router_id or not afi: + return requests + + def update_requests(attr, payload): + url = keypath + ip_path + self.vrrp_config_path[attr].format(vrid=virtual_router_id) + return {'path': url, 'method': PATCH, 'data': payload} + + payload = { + 'openconfig-if-ip:vrrp': { + 'vrrp-group': + [ + { + 'virtual-router-id': virtual_router_id, + 'config': {'virtual-router-id': virtual_router_id} + } + ] + } + } + + url = keypath + ip_path + self.vrrp_config_path['virtual_router_id'] + + requests.append({'path': url, 'method': PATCH, 'data': payload}) + + if vip_addresses: + requests.append(update_requests('virtual_address', {'openconfig-if-ip:virtual-address': vip_addresses})) + + if preempt is not None: + requests.append(update_requests('preempt', {'openconfig-if-ip:preempt': preempt})) + + if advertisement_interval: + requests.append(update_requests('advertisement_interval', {'openconfig-if-ip:advertisement-interval': advertisement_interval})) + + if priority: + requests.append(update_requests('priority', {'openconfig-if-ip:priority': priority})) + + if version: + requests.append(update_requests('version', {'openconfig-interfaces-ext:version': version})) + + if use_v2_checksum is not None: + requests.append(update_requests('use_v2_checksum', {'openconfig-if-ip:use-v2-checksum': use_v2_checksum})) + + if track_interfaces: + for track in track_interfaces: + payload = { + 'openconfig-interfaces-ext:vrrp-track': { + 'vrrp-track-interface': [ + { + 'track-intf': track['interface'], + 'config': { + 'track-intf': track['interface'], + 'priority-increment': int(track['priority_increment']), + }, + } + ] + } + } + requests.append(update_requests('track_interface', payload)) + return requests + + def get_delete_vrrp_commands_requests(self, want, have, is_delete_all): + """ Get list of requests to delete VRRP and VRRP6 configurations + for all interfaces specified by the commands + """ + commands, requests = [], [] + for cmd in want: + del_cmd = {} + name = cmd.get('name') + intf_name = name.replace('/', '%2f') + match_have = next((cnf for cnf in have if cnf['name'] == name), None) + group_list = [] if cmd.get('group') is None else cmd['group'] + + if is_delete_all: + if match_have: + if match_have.get('group'): + del_group, request = self.get_delete_all_vrrp_groups_commands_requests(match_have['group'], intf_name) + if del_group: + commands.append({'name': name}) + requests.extend(request) + else: + del_groups = [] + match_group_list = [] if not match_have else match_have.get('group', []) + if group_list: + for group in group_list: + del_group = {} + virtual_router_id = group.get('virtual_router_id') + afi = group.get('afi') + match_group = next((g for g in match_group_list if g['virtual_router_id'] == virtual_router_id and g['afi'] == afi), None) + if match_group: + del_group = None + if len(match_group.keys()) == 2: + del_group, request = self.get_delete_vrrp_group_command_request(intf_name, virtual_router_id, afi) + else: + del_group, request = self.get_delete_specific_vrrp_param_commands_requests(match_group, virtual_router_id, group, intf_name) + + if del_group: + del_groups.append(del_group) + requests.extend(request) + if del_groups: + del_cmd['group'] = del_groups + elif match_group_list: + del_group, request = self.get_delete_all_vrrp_groups_commands_requests(match_group_list, intf_name) + if del_group: + commands.append({'name': name}) + requests.extend(request) + + if del_cmd: + del_cmd['name'] = name + commands.append(del_cmd) + return commands, requests + + def get_delete_all_vrrp_groups_commands_requests(self, groups, intf_name): + commands, requests, last_vrrp = [], [], [] + groups = [] if groups is None else groups + for group in groups: + virtual_router_id = group.get('virtual_router_id') + afi = group.get('afi') + # VRRP with VRRP ID 1 can be removed only if other VRRP + # groups are removed first + # Hence the check + command, request = self.get_delete_vrrp_group_command_request(intf_name, virtual_router_id, afi) + if command: + commands.append(command) + if virtual_router_id == 1: + last_vrrp.extend(request) + else: + requests.extend(request) + if last_vrrp: + requests.extend(last_vrrp) + + return commands, requests + + def get_delete_vrrp_group_command_request(self, intf_name, virtual_router_id, afi): + """ Get list of requests to delete the entire VRRP and VRRP6 group configurations + based on the specified interface + """ + command, request = [], [] + if not virtual_router_id or not afi: + return command, request + + ip_path = IPV4_PATH if afi == 'ipv4' else IPV6_PATH + if 'Vlan' in intf_name: + keypath = self.vrrp_vlan_path.format(intf_name=intf_name) + else: + parent_intf, sub_intf = intf_name.split('.') if '.' in intf_name else (intf_name, 0) + keypath = self.vrrp_intf_path.format(intf_name=parent_intf, intf_index=sub_intf) + url = '{0}{1}vrrp/vrrp-group={2}'.format(keypath, ip_path, virtual_router_id) + command = {'virtual_router_id': virtual_router_id, 'afi': afi} + request.append({'path': url, 'method': DELETE}) + + return command, request + + def get_delete_specific_vrrp_param_commands_requests(self, cfg_group, virtual_router_id, group, intf_name): + """ Get list of requests to delete VRRP and VRRP6 configurations + based on the command specified for the interface + """ + commands, requests = {}, [] + + afi = group['afi'] + vip_addresses = self.get_vip_addresses(group.get('virtual_address')) + preempt = group.get('preempt') + ip_path = IPV4_PATH if afi == 'ipv4' else IPV6_PATH + adv_interval = group.get('advertisement_interval') + priority = group.get('priority') + version = group.get('version') + use_v2_checksum = group.get('use_v2_checksum') + track_interfaces = self.get_track_interfaces(group.get('track_interface')) + + if not virtual_router_id or not afi: + return commands, requests + + cfg_vip_addresses = self.get_vip_addresses(cfg_group.get('virtual_address')) + cfg_track_interfaces = self.get_track_interfaces(cfg_group.get('track_interface')) + + if 'Vlan' in intf_name: + keypath = self.vrrp_vlan_path.format(intf_name=intf_name) + else: + parent_intf, sub_intf = intf_name.split('.') if '.' in intf_name else (intf_name, 0) + keypath = self.vrrp_intf_path.format(intf_name=parent_intf, intf_index=sub_intf) + + if not (vip_addresses or preempt or adv_interval or priority or version or use_v2_checksum or track_interfaces): + url = keypath + ip_path + 'vrrp/vrrp-group={vrid}'.format(vrid=virtual_router_id) + commands = {'virtual_router_id': virtual_router_id, 'afi': afi} + requests.append({'path': url, 'method': DELETE}) + return commands, requests + + if vip_addresses and cfg_vip_addresses: + del_vip_list = [] + for addr in set(vip_addresses).intersection(set(cfg_vip_addresses)): + del_url = keypath + ip_path + self.vrrp_config_path['virtual_address'].format(vrid=virtual_router_id) + '=' + addr + del_vip_list.append({'address': addr}) + requests.append({'path': del_url, 'method': DELETE}) + if del_vip_list: + commands['virtual_address'] = del_vip_list + + for attr in ('preempt', 'advertisement_interval', 'priority', 'version', 'use_v2_checksum'): + if group.get(attr) is not None and cfg_group.get(attr) is not None and group[attr] == cfg_group[attr]: + requests.append({'path': keypath + ip_path + self.vrrp_config_path[attr].format(vrid=virtual_router_id), 'method': DELETE}) + commands[attr] = group[attr] + + if track_interfaces and cfg_track_interfaces: + del_track_list = [] + for track in track_interfaces: + interface = track['interface'] + interface = interface.replace('/', '%2f') + for cfg_track in cfg_track_interfaces: + cfg_interface = cfg_track['interface'] + cfg_interface = cfg_interface.replace('/', '%2f') + if interface == cfg_interface: + track_url = self.vrrp_config_path['track_interface'].format(vrid=virtual_router_id) + '/vrrp-track-interface=' + interface + requests.append({'path': keypath + ip_path + track_url, 'method': DELETE}) + del_track_list.append({'interface': track['interface'], 'priority_increment': track.get('priority_increment')}) + if del_track_list: + commands['track_interface'] = del_track_list + if commands: + commands['virtual_router_id'] = virtual_router_id + commands['afi'] = afi + return commands, requests + + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: x['name']) + for cfg in config: + if cfg.get('group'): + cfg['group'].sort(key=lambda x: (x['virtual_router_id'], x['afi'])) + for group in cfg['group']: + if group.get('virtual_address'): + group['virtual_address'].sort(key=lambda x: x['address']) + if group.get('track_interface'): + group['track_interface'].sort(key=lambda x: x['interface']) + + @staticmethod + def get_vip_addresses(vip_addresses_list): + """ Get a set of virtual IP/IPv6 addresses available in the given + vip_addresses list + """ + vip_addresses = [] + if not vip_addresses_list: + return vip_addresses + + for addr in vip_addresses_list: + if addr.get('address'): + vip_addresses.append(addr['address']) + + return vip_addresses + + @staticmethod + def get_track_interfaces(track_interfaces_list): + """ Get a set of track interface groups available in the given + track_interfaces list + """ + track_interfaces = [] + if not track_interfaces_list: + return track_interfaces + + for track_interface in track_interfaces_list: + if track_interface['interface'] and track_interface['priority_increment']: + track_interfaces.append(track_interface) + + return track_interfaces + + @staticmethod + def _get_generated_config(commands, have, state): + """Get generated config""" + new_config = remove_empties_from_list(get_new_config(commands, have, TEST_KEYS_formatted_diff)) + if new_config: + for conf in new_config: + # Add default values for after(generated) + groups = conf.get('group', []) + for group in groups: + afi = group.get('afi') + for option in DEFAULT_ATTRIBUTES: + if option not in group: + if afi == 'ipv6' and option in ('use_v2_checksum', 'version'): + continue + group[option] = DEFAULT_ATTRIBUTES[option] + return new_config + + @staticmethod + def get_all_vrrps(have): + vrrp_groups = [] + for cmd in have: + name = cmd.get('name') + vrrp_groups.append({'name': name}) + return vrrp_groups diff --git a/plugins/module_utils/network/sonic/facts/aaa/aaa.py b/plugins/module_utils/network/sonic/facts/aaa/aaa.py index 541a5805e..8a2b6a3ae 100644 --- a/plugins/module_utils/network/sonic/facts/aaa/aaa.py +++ b/plugins/module_utils/network/sonic/facts/aaa/aaa.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ @@ -9,7 +9,6 @@ for a given resource, parsed, and the facts tree is populated based on the configuration. """ - from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -18,13 +17,16 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.aaa.aaa import AaaArgs from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( to_request, edit_config ) -from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.aaa.aaa import AaaArgs -GET = "get" +AAA_PATH = '/data/openconfig-system:system/aaa' class AaaFacts(object): @@ -45,20 +47,6 @@ def __init__(self, module, subspec='config', options='options'): self.generated_spec = utils.generate_dict(facts_argument_spec) - def get_aaa(self): - """Get aaa details available in chassis""" - request = [{"path": "data/openconfig-system:system/aaa", "method": GET}] - try: - response = edit_config(self._module, to_request(self._module, request)) - except ConnectionError as exc: - self._module.fail_json(msg=str(exc), code=exc.code) - data = {} - if ('openconfig-system:aaa' in response[0][1]): - if ('authentication' in response[0][1]['openconfig-system:aaa']): - if ('config' in response[0][1]['openconfig-system:aaa']['authentication']): - data = response[0][1]['openconfig-system:aaa']['authentication']['config'] - return data - def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for aaa :param connection: the device connection @@ -67,44 +55,94 @@ def populate_facts(self, connection, ansible_facts, data=None): :rtype: dictionary :returns: facts """ - if not data: - data = self.get_aaa() objs = [] - objs = self.render_config(self.generated_spec, data) + + if not data: + data = self.update_aaa(self._module) + objs = data facts = {} if objs: params = utils.validate_config(self.argument_spec, {'config': objs}) - facts['aaa'] = params['config'] - + facts['aaa'] = remove_empties(params['config']) ansible_facts['ansible_network_resources'].update(facts) return ansible_facts - def render_config(self, spec, conf): - """ - Render config as dictionary structure and delete keys - from spec for null values + def get_config(self, module, path, key_name): + """Retrieve OC configuration from device""" + cfg = None + get_path = '%s/%s' % (AAA_PATH, path) + request = {'path': get_path, 'method': 'get'} - :param spec: The facts tree, generated from the argspec - :param conf: The configuration - :rtype: dictionary - :returns: The generated config - """ - config = self.parse_sonic_aaa(spec, conf) - return config - - def parse_sonic_aaa(self, spec, conf): - config = deepcopy(spec) - if conf: - temp = {} - if ('authentication-method' in conf) and (conf['authentication-method']): - if 'local' in conf['authentication-method']: - temp['local'] = True - choices = ['tacacs+', 'ldap', 'radius'] - for i, word in enumerate(conf['authentication-method']): - if word in choices: - temp['group'] = conf['authentication-method'][i] - if ('failthrough' in conf): - temp['fail_through'] = conf['failthrough'] - if temp: - config['authentication']['data'] = temp - return utils.remove_empties(config) + try: + response = edit_config(module, to_request(module, request)) + if key_name in response[0][1]: + cfg = response[0][1].get(key_name) + except Exception as exc: + # Avoid raising error when there is no configuration + if 'Resource not found' in str(exc): + pass + else: + module.fail_json(msg=str(exc), code=exc.code) + + return cfg + + def update_aaa(self, module): + """Transform OC configuration to Ansible argspec""" + config_dict = {} + bool_dict = {'True': True, 'False': False} + + # Authentication configuration handling + authentication_cfg = self.get_config(module, 'authentication/config', 'openconfig-system:config') + if authentication_cfg: + authentication_dict = {} + auth_method = authentication_cfg.get('authentication-method') + console_auth_local = authentication_cfg.get('console-authentication-local') + failthrough = authentication_cfg.get('failthrough') + + if auth_method: + authentication_dict['auth_method'] = auth_method + if console_auth_local is not None: + authentication_dict['console_auth_local'] = console_auth_local + if failthrough: + authentication_dict['failthrough'] = bool_dict[failthrough] + if authentication_dict: + config_dict['authentication'] = authentication_dict + + # Authorization configuration handling + authorization_dict = {} + commands_auth_method = self.get_config(module, 'authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method', + 'openconfig-aaa-tacacsplus-ext:authorization-method') + login_auth_method = self.get_config(module, 'authorization/openconfig-aaa-ext:login/config/authorization-method', + 'openconfig-aaa-ext:authorization-method') + + if commands_auth_method: + authorization_dict['commands_auth_method'] = commands_auth_method + if login_auth_method: + authorization_dict['login_auth_method'] = login_auth_method + if authorization_dict: + config_dict['authorization'] = authorization_dict + + # Name-service configuration handling + name_service_cfg = self.get_config(module, 'openconfig-aaa-ext:name-service/config', 'openconfig-aaa-ext:config') + if name_service_cfg: + name_service_dict = {} + group = name_service_cfg.get('group-method') + netgroup = name_service_cfg.get('netgroup-method') + passwd = name_service_cfg.get('passwd-method') + shadow = name_service_cfg.get('shadow-method') + sudoers = name_service_cfg.get('sudoers-method') + + if group: + name_service_dict['group'] = group + if netgroup: + name_service_dict['netgroup'] = netgroup + if passwd: + name_service_dict['passwd'] = passwd + if shadow: + name_service_dict['shadow'] = shadow + if sudoers: + name_service_dict['sudoers'] = sudoers + if name_service_dict: + config_dict['name_service'] = name_service_dict + + return config_dict diff --git a/plugins/module_utils/network/sonic/facts/bgp/bgp.py b/plugins/module_utils/network/sonic/facts/bgp/bgp.py index 7ecf253e7..c8d00e60d 100644 --- a/plugins/module_utils/network/sonic/facts/bgp/bgp.py +++ b/plugins/module_utils/network/sonic/facts/bgp/bgp.py @@ -48,7 +48,8 @@ class BgpFacts(object): 'admin_max_med': ['max-med', 'admin-max-med-val'], 'max_med_on_startup_timer': ['max-med', 'time'], 'max_med_on_startup_med_val': ['max-med', 'max-med-val'], - 'rt_delay': 'route-map-process-delay' + 'rt_delay': 'route-map-process-delay', + 'as_notation': 'as-notation' } def __init__(self, module, subspec='config', options='options'): diff --git a/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py b/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py index f1b26bf9a..1cbe008d4 100644 --- a/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py +++ b/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py @@ -68,7 +68,8 @@ class Bgp_afFacts(object): 'rt_out': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'export-rts'], 'vnis': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:vnis', 'vni'], 'import_vrf_list': ['openconfig-bgp-ext:import-network-instance', 'config', 'name'], - 'import_vrf_route_map': ['openconfig-bgp-ext:import-network-instance', 'config', 'policy-name'] + 'import_vrf_route_map': ['openconfig-bgp-ext:import-network-instance', 'config', 'policy-name'], + 'aggregate_address_config': ['aggregate-address-config', 'aggregate-address'] } af_redis_params_map = { @@ -210,17 +211,43 @@ def update_network(self, data): if afs: for af in afs: temp = [] - network = af.get('network', None) + network = af.get('network') + dampening = af.get('dampening') + aggregate_address_config = af.get('aggregate_address_config') + if network: for e in network: - prefix = e.get('prefix', None) + prefix = e.get('prefix') if prefix: temp.append(prefix) af['network'] = temp - dampening = af.get('dampening', None) if dampening: af.pop('dampening') af['dampening'] = dampening + if aggregate_address_config: + addr_list = [] + for addr in aggregate_address_config: + addr_dict = {} + prefix = addr.get('prefix') + config = addr.get('config') + + if prefix: + addr_dict['prefix'] = prefix + if config: + as_set = config.get('as-set') + policy_name = config.get('policy-name') + summary_only = config.get('summary-only') + + if as_set is not None: + addr_dict['as_set'] = as_set + if policy_name: + addr_dict['policy_name'] = policy_name + if summary_only is not None: + addr_dict['summary_only'] = summary_only + if addr_dict: + addr_list.append(addr_dict) + if addr_list: + af['aggregate_address_config'] = addr_list def update_afis(self, data): for conf in data: diff --git a/plugins/module_utils/network/sonic/facts/facts.py b/plugins/module_utils/network/sonic/facts/facts.py index 1e8475fc0..2aacd4988 100644 --- a/plugins/module_utils/network/sonic/facts/facts.py +++ b/plugins/module_utils/network/sonic/facts/facts.py @@ -30,14 +30,18 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_ext_communities.bgp_ext_communities import ( Bgp_ext_communitiesFacts, ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospfv2_interfaces.ospfv2_interfaces import Ospfv2_interfacesFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospfv2.ospfv2 import Ospfv2Facts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mclag.mclag import MclagFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.prefix_lists.prefix_lists import Prefix_listsFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vlan_mapping.vlan_mapping import Vlan_mappingFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vrfs.vrfs import VrfsFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vrrp.vrrp import VrrpFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vxlans.vxlans import VxlansFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.users.users import UsersFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.port_breakout.port_breakout import Port_breakoutFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.aaa.aaa import AaaFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ldap.ldap import LdapFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.tacacs_server.tacacs_server import Tacacs_serverFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.system.system import SystemFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.radius_server.radius_server import Radius_serverFacts @@ -72,6 +76,8 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.pim_interfaces.pim_interfaces import Pim_interfacesFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.login_lockout.login_lockout import Login_lockoutFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.poe.poe import PoeFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mgmt_servers.mgmt_servers import Mgmt_serversFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospf_area.ospf_area import Ospf_areaFacts FACT_LEGACY_SUBSETS = {} FACT_RESOURCE_SUBSETS = dict( @@ -87,15 +93,19 @@ bgp_as_paths=Bgp_as_pathsFacts, bgp_communities=Bgp_communitiesFacts, bgp_ext_communities=Bgp_ext_communitiesFacts, + ospfv2_interfaces=Ospfv2_interfacesFacts, + ospfv2=Ospfv2Facts, mclag=MclagFacts, prefix_lists=Prefix_listsFacts, vlan_mapping=Vlan_mappingFacts, vrfs=VrfsFacts, + vrrp=VrrpFacts, vxlans=VxlansFacts, users=UsersFacts, system=SystemFacts, port_breakout=Port_breakoutFacts, aaa=AaaFacts, + ldap=LdapFacts, tacacs_server=Tacacs_serverFacts, radius_server=Radius_serverFacts, static_routes=Static_routesFacts, @@ -128,7 +138,9 @@ pim_global=Pim_globalFacts, pim_interfaces=Pim_interfacesFacts, login_lockout=Login_lockoutFacts, - poe=PoeFacts + poe=PoeFacts, + mgmt_servers=Mgmt_serversFacts, + ospf_area=Ospf_areaFacts ) diff --git a/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py b/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py index 78d5b002e..70763bba1 100644 --- a/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py @@ -60,14 +60,16 @@ def get_l2_interfaces_from_interfaces(self, interfaces): l2_interfaces = [] for intf in interfaces: - name = intf['name'] + name = intf.get('name') + if not name: + continue key = 'openconfig-if-ethernet:ethernet' if name.startswith('PortChannel'): key = 'openconfig-if-aggregate:aggregation' eth_det = intf.get(key) if eth_det: open_cfg_vlan = eth_det.get('openconfig-vlan:switched-vlan') - if open_cfg_vlan: + if open_cfg_vlan and 'config' in open_cfg_vlan: new_det = dict() new_det['name'] = name if name == "eth0": diff --git a/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py index e91a2b033..5ffddfd73 100644 --- a/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py @@ -99,14 +99,22 @@ def get_l3_interfaces(self): if 'openconfig-if-ip:ipv6' in ip: if 'addresses' in ip['openconfig-if-ip:ipv6'] and 'address' in ip['openconfig-if-ip:ipv6']['addresses']: for ipv6 in ip['openconfig-if-ip:ipv6']['addresses']['address']: - if ipv6.get('config') and ipv6.get('config').get('ip'): + if ipv6.get('config'): temp = dict() - temp['address'] = str(ipv6['config']['ip']) + '/' + str(ipv6['config']['prefix-length']) - l3_ipv6.append(temp) + if ipv6.get('config').get('ip'): + temp['address'] = str(ipv6['config']['ip']) + '/' + str(ipv6['config']['prefix-length']) + if ipv6.get('config').get('openconfig-interfaces-private:eui64'): + temp['eui64'] = ipv6['config']['openconfig-interfaces-private:eui64'] + if temp: + l3_ipv6.append(temp) if l3_ipv6: l3_dict['ipv6']['addresses'] = l3_ipv6 if 'config' in ip['openconfig-if-ip:ipv6'] and 'enabled' in ip['openconfig-if-ip:ipv6']['config']: l3_dict['ipv6']['enabled'] = ip['openconfig-if-ip:ipv6']['config']['enabled'] + if 'config' in ip['openconfig-if-ip:ipv6'] and 'ipv6_autoconfig' in ip['openconfig-if-ip:ipv6']['config']: + l3_dict['ipv6']['autoconf'] = ip['openconfig-if-ip:ipv6']['config']['ipv6_autoconfig'] + if 'config' in ip['openconfig-if-ip:ipv6'] and 'ipv6_dad' in ip['openconfig-if-ip:ipv6']['config']: + l3_dict['ipv6']['dad'] = ip['openconfig-if-ip:ipv6']['config']['ipv6_dad'] l3_configs.append(l3_dict) return l3_configs diff --git a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py index d83659d92..cf97879e5 100644 --- a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py @@ -57,6 +57,10 @@ def get_all_portchannels(self): data = response[0][1]['sonic-portchannel:sonic-portchannel'] else: data = [] + + return data + + def get_po_and_po_members(self, data): if data is not None: if "PORTCHANNEL_MEMBER" in data: portchannel_members_list = data["PORTCHANNEL_MEMBER"]["PORTCHANNEL_MEMBER_LIST"] @@ -75,6 +79,13 @@ def get_all_portchannels(self): else: return [] + def get_ethernet_segments(self, data): + es_list = [] + if data: + if "EVPN_ETHERNET_SEGMENT" in data: + es_list = data["EVPN_ETHERNET_SEGMENT"]["EVPN_ETHERNET_SEGMENT_LIST"] + return es_list + def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for lag_interfaces :param connection: the device connection @@ -86,13 +97,43 @@ def populate_facts(self, connection, ansible_facts, data=None): objs = [] if not data: data = self.get_all_portchannels() - # operate on a collection of resource x - for conf in data: + + po_data = self.get_po_and_po_members(data) + for conf in po_data: if conf: obj = self.render_config(self.generated_spec, conf) obj = self.transform_config(obj) if obj: self.merge_portchannels(objs, obj) + + es_data = self.get_ethernet_segments(data) + for es in es_data: + po_name = es['ifname'] + esi_t = es['esi_type'] + esi = es['esi'] + if 'df_pref' in es: + df_pref = es['df_pref'] + else: + df_pref = None + + if esi_t == 'TYPE_1_LACP_BASED': + esi_type = 'auto_lacp' + elif esi_t == 'TYPE_3_MAC_BASED': + esi_type = 'auto_system_mac' + elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': + esi_type = 'ethernet_segment_id' + + if df_pref: + es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} + else: + es_dict = {'esi_type': esi_type, 'esi': esi} + + have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) + if have_po_conf: + have_po_conf['ethernet_segment'] = es_dict + else: + self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) + facts = {} if objs: facts['lag_interfaces'] = [] @@ -100,6 +141,7 @@ def populate_facts(self, connection, ansible_facts, data=None): for cfg in params['config']: facts['lag_interfaces'].append(cfg) ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts def render_config(self, spec, conf): diff --git a/plugins/module_utils/network/sonic/facts/ldap/__init__.py b/plugins/module_utils/network/sonic/facts/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/ldap/ldap.py b/plugins/module_utils/network/sonic/facts/ldap/ldap.py new file mode 100644 index 000000000..98d32ddf6 --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/ldap/ldap.py @@ -0,0 +1,274 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic ldap fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils \ + import ( + remove_empties_from_list + ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ldap.ldap import LdapArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible.module_utils.connection import ConnectionError + +LDAP_GROUPS = {'LDAP': 'global', 'LDAP_NSS': 'nss', 'LDAP_PAM': 'pam', 'LDAP_SUDO': 'sudo'} +CONFIG_ATTRIBUTES = { + 'base': 'base', + 'bind-dn': 'binddn', + 'bind-pw': 'bindpw', + 'bind-time-limit': 'bind_timelimit', + 'port': 'port', + 'search-time-limit': 'timelimit', + 'ssl': 'ssl', + 'retransmit-attempts': 'retry', + 'version': 'version' +} + +ONLY_NSS_ATTRIBUTES = { + 'nss-base-group': 'nss_base_group', + 'nss-base-netgroup': 'nss_base_netgroup', + 'nss-base-passwd': 'nss_base_passwd', + 'nss-base-shadow': 'nss_base_shadow', + 'nss-base-sudoers': 'nss_base_sudoers', + 'nss-initgroups-ignoreusers': 'nss_initgroups_ignoreusers', + 'scope': 'scope', + 'idle-time-limit': 'idle_timelimit' +} + +ONLY_PAM_ATTRIBUTES = { + 'pam-filter': 'pam_filter', + 'pam-group-dn': 'pam_group_dn', + 'pam-login-attribute': 'pam_login_attribute', + 'pam-member-attribute': 'pam_member_attribute', + 'scope': 'scope', + 'nss-base-passwd': 'nss_base_passwd', +} + +ONLY_SUDO_ATTRIBUTES = { + 'sudoers-base': 'sudoers_base', + 'sudoers-search-filter': 'sudoers_search_filter' +} + +MAP_ATTRIBUTES = { + 'ATTRIBUTE': 'attribute', + 'DEFAULT_ATTRIBUTE_VALUE': 'default_attribute', + 'OBJECTCLASS': 'objectclass', + 'OVERRIDE_ATTRIBUTE_VALUE': 'override_attribute', + 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE': 'map_remote_groups_to_sonic_roles' +} + +SERVER_ATTRIBUTES = { + 'use-type': 'server_type', + 'port': 'port', + 'ssl': 'ssl', + 'priority': 'priority', + 'retransmit-attempts': 'retry' +} + + +class LdapFacts(object): + """ The sonic ldap fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = LdapArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ldap + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if connection: # just for linting purposes, remove + pass + + all_ldap_configs = {} + + if not data: + all_ldap_configs = self.get_ldap() + + for ldap_config in all_ldap_configs: + obj = self.render_config(self.generated_spec, ldap_config) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('ldap', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['ldap'] = remove_empties_from_list(params['config']) + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + return conf + + def get_ldap(self): + request = [{"path": 'data/openconfig-system:system/aaa/server-groups', "method": "GET"}] + + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + + ldap_configs = [] + + if 'openconfig-system:server-groups' in response[0][1]: + server_groups = response[0][1]['openconfig-system:server-groups'] + if server_groups and server_groups.get('server-group'): + server_groups = server_groups['server-group'] + for server_group in server_groups: + name = server_group.get('name') + servers = server_group.get('servers', {}) + if "LDAP" in name: + ldap_config = {} + if "openconfig-aaa-ldap-ext:ldap" in server_group: + if name == "LDAP": + ldap_config = self.get_ldap_global_config(server_group['openconfig-aaa-ldap-ext:ldap'], servers) + if "config" in server_group: + if server_group['config'].get('source-interface'): + ldap_config['source_interface'] = server_group['config']['source-interface'] + elif name == "LDAP_NSS": + ldap_config = self.get_ldap_subtype_config(server_group['openconfig-aaa-ldap-ext:ldap'], 'NSS') + elif name == "LDAP_PAM": + ldap_config = self.get_ldap_subtype_config(server_group['openconfig-aaa-ldap-ext:ldap'], 'PAM') + elif name == "LDAP_SUDO": + ldap_config = self.get_ldap_subtype_config(server_group['openconfig-aaa-ldap-ext:ldap'], 'SUDO') + if ldap_config: + ldap_config['name'] = LDAP_GROUPS[name] + ldap_configs.append(ldap_config) + return ldap_configs + + def get_ldap_global_config(self, ldap_config, servers): + ATTRIBUTES = { + "vrf-name": "vrf", + "nss-skipmembers": "nss_skipmembers" + } + global_config, map_config = {}, {} + config = ldap_config.get('config', []) + maps = ldap_config.get('maps', {}) + + for cfg in config: + if cfg not in ("bind-pw", "encrypted"): + attribute = CONFIG_ATTRIBUTES.get(cfg) + attribute = attribute or ONLY_NSS_ATTRIBUTES.get(cfg) + attribute = attribute or ONLY_PAM_ATTRIBUTES.get(cfg) + attribute = attribute or ONLY_SUDO_ATTRIBUTES.get(cfg) + attribute = attribute or ATTRIBUTES.get(cfg) + if attribute: + global_config[attribute] = config[cfg].lower() if attribute in ("ssl", "scope") else config[cfg] + else: + if 'bind-pw' in config and config.get('bind-pw') not in (None, ''): + global_config.setdefault("bindpw", {}) + global_config['bindpw']['pwd'] = config['bind-pw'] + global_config['bindpw']['encrypted'] = config['encrypted'] + if maps: + maps = maps.get('map', []) + for map in maps: + cfg = map.get('config') + if cfg: + from_value = cfg.get("from") + to_value = cfg.get("to") + attr = cfg.get("name") + if from_value and to_value and attr: + map_config.setdefault(MAP_ATTRIBUTES[attr], []) + if attr != 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE': + map_config[MAP_ATTRIBUTES[attr]].append({"from": from_value, "to": to_value}) + else: + sonic_roles = [] + roles = to_value.split(",") + for role in roles: + sonic_roles.append(role) + map_config[MAP_ATTRIBUTES[attr]].append({"remote_group": from_value, "sonic_roles": sonic_roles}) + + if map_config: + global_config['map'] = map_config + + if servers: + servers = servers.get('server', []) + server_config = self.get_server_config(servers) + if server_config: + global_config['servers'] = server_config + + return global_config + + def get_ldap_subtype_config(self, ldap_config, ldap_subtype): + subtype_config = {} + enum_subtype = { + 'NSS': ONLY_NSS_ATTRIBUTES, + 'PAM': ONLY_PAM_ATTRIBUTES, + 'SUDO': ONLY_SUDO_ATTRIBUTES + } + config = ldap_config.get('config') + for cfg in config: + if cfg not in ("bind-pw", "encrypted"): + attribute = CONFIG_ATTRIBUTES.get(cfg) + attribute = attribute or enum_subtype[ldap_subtype].get(cfg) + if attribute: + subtype_config[attribute] = config[cfg].lower() if attribute in ("ssl", "scope") else config[cfg] + else: + if 'bind-pw' in config and config.get('bind-pw') not in (None, ''): + subtype_config.setdefault("bindpw", {}) + subtype_config['bindpw']['pwd'] = config['bind-pw'] + subtype_config['bindpw']['encrypted'] = config['encrypted'] + + return subtype_config + + def get_server_config(self, servers): + server_configs = [] + for server in servers: + server_config = {} + address = server.get('address') + if address: + server_config['address'] = address + config = server.get('openconfig-aaa-ldap-ext:ldap', {}) + if config: + config = config.get('config', {}) + for cfg in config: + attribute = SERVER_ATTRIBUTES.get(cfg) + if attribute: + server_config[attribute] = config[cfg].lower() if attribute in ("ssl", "server_type") else config[cfg] + if server_config: + server_configs.append(server_config) + return server_configs diff --git a/plugins/module_utils/network/sonic/facts/mgmt_servers/mgmt_servers.py b/plugins/module_utils/network/sonic/facts/mgmt_servers/mgmt_servers.py new file mode 100644 index 000000000..c1a033d42 --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/mgmt_servers/mgmt_servers.py @@ -0,0 +1,101 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic mgmt_servers fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mgmt_servers.mgmt_servers import Mgmt_serversArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) + +SYS_PATH = '/data/openconfig-system:system' + + +class Mgmt_serversFacts(object): + """ The sonic mgmt_servers fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Mgmt_serversArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for mgmt_servers + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + if not data: + data = self.update_mgmt_servers(self._module) + objs = data + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['mgmt_servers'] = remove_empties(params['config']) + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def update_mgmt_servers(self, module): + """Transform OC configuration to Ansible argspec""" + config_dict = {} + + rest_cfg = self.get_config(module, 'rest-server/config') + telemetry_cfg = self.get_config(module, 'telemetry-server/config') + + if rest_cfg: + # Change from OC naming to Ansible naming + if rest_cfg.get('openconfig-system-mgmt-servers:disable') is not None: + rest_cfg['shutdown'] = rest_cfg.get('openconfig-system-mgmt-servers:disable') + rest_cfg.pop('openconfig-system-mgmt-servers:disable') + config_dict['rest'] = rest_cfg + if telemetry_cfg: + config_dict['telemetry'] = telemetry_cfg + + return config_dict + + def get_config(self, module, path): + """Retrieve OC configuration from device""" + cfg = None + get_path = '%s/%s' % (SYS_PATH, path) + request = {'path': get_path, 'method': 'get'} + + try: + response = edit_config(module, to_request(module, request)) + if 'openconfig-system:config' in response[0][1]: + cfg = response[0][1].get('openconfig-system:config') + except ConnectionError as exc: + module.fail_json(msg=str(exc), code=exc.code) + + return cfg diff --git a/plugins/module_utils/network/sonic/facts/ospf_area/ospf_area.py b/plugins/module_utils/network/sonic/facts/ospf_area/ospf_area.py new file mode 100644 index 000000000..a9abde4c3 --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/ospf_area/ospf_area.py @@ -0,0 +1,231 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic ospf_area fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + validate_config, + generate_dict +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic \ + import to_request, edit_config + +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + get_all_vrfs +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospf_area.ospf_area import Ospf_areaArgs + + +class Ospf_areaFacts(object): + """ The sonic ospf_area fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ospf_areaArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ospf_area + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + if not data: + data = self.get_ospf_info() + + data = self.render_config(data) + data = {"config": data} + # validate can add empties to config where values do not really exist + cleaned_data = remove_empties( + validate_config(self.argument_spec, data) + ) + if "state" in cleaned_data: + del cleaned_data["state"] + if "config" not in cleaned_data: + cleaned_data["config"] = [] + + ansible_facts['ansible_network_resources'].pop('ospf_area', None) + if cleaned_data: + ansible_facts['ansible_network_resources'].update({"ospf_area": cleaned_data}) + + return ansible_facts + + def render_config(self, data): + '''Takes REST "GET" data fetched from device and returns a copy that is formatted like argspec. + The input is assumed to be a dict with vrf names as keys. The 'values' for each VRF contain + JSON data for the areas of that vrf. + :rtype: dictionary + :returns: dictionary that has options in same format as defined in argspec. + Note returned dict also has the config key in shown argspec, but not the state key''' + formatted_data = {} + ospf_key_ext = "openconfig-ospfv2-ext:" + for vrf, ospf_settings in data.items(): + # go through each area for this vrf + for area in ospf_settings.get("areas", {}).get("area", []): + if "identifier" not in area: + # these are indentifying keys, area is invalid if doesn't have it + self._module.fail_json(msg="area for vrf {vrf} retrieved from device is missing identifier".format(vrf=vrf)) + formatted_area = {} + # only try grabbing settings in the JSON subsections if the sections exist + if "config" in area: + # authentication type should only have these two values + if area["config"].get(ospf_key_ext + "authentication-type") == "MD5HMAC": + formatted_area["authentication_type"] = "message_digest" + elif area["config"].get(ospf_key_ext + "authentication-type") == "TEXT": + formatted_area["authentication_type"] = "text" + # doing if check on shortcut since it can be just lowecased if it is inside, + # a none return will cause issues + if ospf_key_ext + "shortcut" in area["config"]: + formatted_area["shortcut"] = area["config"][ospf_key_ext + "shortcut"].replace("openconfig-ospfv2-ext:", "").lower() + + if ospf_key_ext + "stub" in area and "config" in area[ospf_key_ext + "stub"]: + # If a value is currently configured for the stub 'default-cost' attribute, + # store it in the output 'facts' as the 'default_cost' for the area. + # Note: If OSPF NSSA is implemented for SONIC in the future with + # a different configurable "default cost" value, edits will need to be made. + formatted_area["default_cost"] = area[ospf_key_ext + "stub"]["config"].get("default-cost") + # other settings are under stub subsection + formatted_area["stub"] = {} + formatted_area["stub"]["enabled"] = area[ospf_key_ext + "stub"]["config"].get("enable") + formatted_area["stub"]["no_summary"] = area[ospf_key_ext + "stub"]["config"].get("no-summary") + if "virtual-links" in area and area["virtual-links"].get("virtual-link"): + formatted_area["virtual_links"] = [] + for vlink_settings in area["virtual-links"]["virtual-link"]: + formatted_virtual_link = {} + if "remote-router-id" not in vlink_settings: + self._module.fail_json(msg="virtual link in area {area}".format(area=area["area_id"]) + + " for vrf {vrf} retrieved from device is missing remote-router-id identifier".format(vrf=vrf)) + formatted_virtual_link["router_id"] = vlink_settings.get('remote-router-id') + + if "config" in vlink_settings: + formatted_virtual_link["enabled"] = vlink_settings["config"].get(ospf_key_ext + "enable") + formatted_virtual_link["dead_interval"] = vlink_settings["config"].get(ospf_key_ext + "dead-interval") + formatted_virtual_link["hello_interval"] = vlink_settings["config"].get(ospf_key_ext + "hello-interval") + formatted_virtual_link["retransmit_interval"] = vlink_settings["config"].get(ospf_key_ext + "retransmission-interval") + formatted_virtual_link["transmit_delay"] = vlink_settings["config"].get(ospf_key_ext + "transmit-delay") + + formatted_vlink_auth = {} + if vlink_settings["config"].get(ospf_key_ext + "authentication-type") == "MD5HMAC": + formatted_vlink_auth["auth_type"] = "message_digest" + elif vlink_settings["config"].get(ospf_key_ext + "authentication-type") == "TEXT": + formatted_vlink_auth["auth_type"] = "text" + # if auth type is none, don't need to display + formatted_vlink_auth["key"] = vlink_settings["config"].get(ospf_key_ext + "authentication-key") + if formatted_vlink_auth["key"]: + # for some reason this stays around + formatted_vlink_auth["key_encrypted"] = vlink_settings["config"].get(ospf_key_ext + "authentication-key-encrypted") + if formatted_vlink_auth: + formatted_virtual_link["authentication"] = formatted_vlink_auth + + if ospf_key_ext + "md-authentications" in vlink_settings and \ + vlink_settings[ospf_key_ext + "md-authentications"].get("md-authentication"): + formatted_virtual_link["message_digest_list"] = [] + for md5_settings in vlink_settings[ospf_key_ext + "md-authentications"]["md-authentication"]: + formatted_md5 = {} + if "authentication-key-id" not in md5_settings: + self._module.fail_json(msg="md authentication key in virtual link" + + " {link} in area {area} for".format(link=vlink_settings["remote-router-id"], area=area["area_id"]) + + " vrf {vrf} retrieved from device is missing authentication-key-id identifier".format(vrf=vrf)) + formatted_md5["key_id"] = md5_settings["authentication-key-id"] + if "config" in md5_settings: + formatted_md5["key"] = md5_settings["config"].get("authentication-md5-key") + formatted_md5["key_encrypted"] = md5_settings["config"].get("authentication-key-encrypted") + formatted_virtual_link["message_digest_list"].append(formatted_md5) + formatted_area["virtual_links"].append(formatted_virtual_link) + if ospf_key_ext + "networks" in area: + formatted_area["networks"] = [] + for network in area[ospf_key_ext + "networks"].get("network", []): + if network.get("address-prefix"): + formatted_area["networks"].append(network["address-prefix"]) + if formatted_area: + formatted_area["area_id"] = area["identifier"] + formatted_area["vrf_name"] = vrf + formatted_data[(vrf, formatted_area["area_id"])] = formatted_area + + for inter_area_policy in ospf_settings.get("global", {}).get("inter-area-propagation-policies", {}).get(ospf_key_ext + "inter-area-policy", []): + # since two separate lists, combining them. doing check of if area found just in case, but area should always be found + # at this point + if "src-area" not in inter_area_policy: + self._module.fail_json(msg="inter area policy for vrf" + + " {vrf} retrieved from device is missing src-area identifier".format(vrf=vrf)) + if (vrf, inter_area_policy["src-area"]) in formatted_data: + formatted_area = formatted_data[(vrf, inter_area_policy["src-area"])] + else: + formatted_area = {} + if "filter-list-in" in inter_area_policy: + formatted_area["filter_list_in"] = inter_area_policy.get("filter-list-in", {}).get("config", {}).get("name") + if "filter-list-out" in inter_area_policy: + formatted_area["filter_list_out"] = inter_area_policy.get("filter-list-out", {}).get("config", {}).get("name") + if "ranges" in inter_area_policy: + formatted_area["ranges"] = [] + for area_range in inter_area_policy["ranges"].get("range"): + if "address-prefix" not in area_range: + self._module.fail_json(msg="range in area {area} for vrf {vrf}".format(area=area["area_id"], vrf=vrf) + + " retrieved from device is missing address-prefix identifier") + formatted_range = {} + formatted_range["prefix"] = area_range["address-prefix"] + if "config" in area_range: + formatted_range["advertise"] = area_range["config"].get("advertise") + formatted_range["cost"] = area_range["config"].get("metric") + # note that substitute is mispelled in openconfig + formatted_range["substitute"] = area_range["config"].get("substitue-prefix") + formatted_area["ranges"].append(formatted_range) + if (vrf, inter_area_policy["src-area"]) not in formatted_data and formatted_area: + # if these fields aren't inside means somehow missed area in areas list but there's inter-area policies for it. + # needed to move adding keys here to prevent reporting area exists all the time including when policies doesn't find any settings + formatted_area["area_id"] = inter_area_policy["src-area"] + formatted_area["vrf_name"] = vrf + formatted_data[(vrf, formatted_area["area_id"])] = formatted_area + return [remove_empties(area) for area in formatted_data.values()] + + def get_ospf_info(self): + '''get the top level of ospf data from device + :rtype: dictionary + :returns: dictionary of vrf name to their ospf settings + ''' + ospf_path = 'data/openconfig-network-instance:network-instances/network-instance={vrf}' + \ + '/protocols/protocol=OSPF,ospfv2/ospfv2' + method = "GET" + + ospf_settings = {} + + vrf_list = get_all_vrfs(self._module) + for vrf in vrf_list: + request = {"path": ospf_path.format(vrf=vrf), "method": method} + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc)) + try: + response_body = response[0][1].get("openconfig-network-instance:ospfv2", {}) + except Exception as exc: + self._module.fail_json(msg=str(exc)) + + if response_body: + ospf_settings[vrf] = response_body + return ospf_settings diff --git a/plugins/module_utils/network/sonic/facts/ospfv2/__init__.py b/plugins/module_utils/network/sonic/facts/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/sonic/facts/ospfv2/ospfv2.py new file mode 100644 index 000000000..c1532eb7f --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/ospfv2/ospfv2.py @@ -0,0 +1,307 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic ospfv2 fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospfv2.ospfv2 import Ospfv2Args +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import ( + get_all_vrfs, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties_from_list +) +from ansible.module_utils.connection import ConnectionError + + +network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance' +protocol_ospf_path = 'protocols/protocol=OSPF,ospfv2/ospfv2' + + +class Ospfv2Facts(object): + """ The sonic ospfv2 fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ospfv2Args.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ospfv2 + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + all_ospfv2_configs = {} + + if not data: + all_ospfv2_configs = self.get_ospfv2(self._module) + + for ospf_config in all_ospfv2_configs: + if ospf_config: + obj = self.render_config(self.generated_spec, ospf_config) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('ospfv2', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['ospfv2'] = remove_empties_from_list(params['config']) + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + return conf + + def get_ospfv2(self, module): + """Get all OSPFv2 configurations available in chassis""" + ospf_configs = [] + vrfs = get_all_vrfs(module) + for vrf_name in vrfs: + all_ospf_path = '%s=%s/%s' % (network_instance_path, vrf_name, protocol_ospf_path) + request = [{"path": all_ospf_path, "method": "GET"}] + + try: + response = edit_config(module, to_request(module, request)) + except ConnectionError as exc: + module.fail_json(msg=str(exc), code=exc.code) + + if 'openconfig-network-instance:ospfv2' in response[0][1]: + ospf_dict = {} + ospf_global = response[0][1]['openconfig-network-instance:ospfv2'].get('global', {}) + ospf_passive_list = ospf_global.get('openconfig-ospfv2-ext:passive-interfaces', {}) + + ospf_dict.update(self.get_ospf_globals(ospf_global)) + ospf_dict.update(self.get_ospf_timers_max_metric(ospf_global)) + ospf_dict.update(self.get_ospf_passive(ospf_passive_list)) + ospf_dict.update(self.get_ospf_redistribute(ospf_global.get("openconfig-ospfv2-ext:route-distribution-policies", {}))) + ospf_dict.update(self.get_ospf_graceful_restart(ospf_global.get("graceful-restart", {}))) + + if ospf_dict: + ospf_dict['vrf_name'] = vrf_name + ospf_configs.append(ospf_dict) + return ospf_configs + + def get_ospf_globals(self, ospf_global): + ospf_dict = {} + config = ospf_global.get("config") + + if config: + self.update_dict(ospf_dict, "router_id", config.get("router-id")) + abr_type = config.get('openconfig-ospfv2-ext:abr-type') + if abr_type: + abr_type = abr_type.split(':')[1] + self.update_dict(ospf_dict, 'abr_type', abr_type.lower()) + self.update_dict(ospf_dict, 'auto_cost_reference_bandwidth', config.get('openconfig-ospfv2-ext:auto-cost-reference-bandwidth')) + self.update_dict(ospf_dict, 'default_metric', config.get('openconfig-ospfv2-ext:default-metric')) + self.update_dict(ospf_dict, 'default_passive', config.get('openconfig-ospfv2-ext:passive-interface-default', False)) + self.update_dict(ospf_dict, 'maximum_paths', config.get('openconfig-ospfv2-ext:maximum-paths')) + self.update_dict(ospf_dict, 'opaque_lsa_capability', config.get('openconfig-ospfv2-ext:opaque-lsa-capability')) + self.update_dict(ospf_dict, 'rfc1583_compatible', config.get('openconfig-ospfv2-ext:ospf-rfc1583-compatible')) + self.update_dict(ospf_dict, 'write_multiplier', config.get('openconfig-ospfv2-ext:write-multiplier')) + self.update_dict(ospf_dict, 'log_adjacency_changes', config.get('openconfig-ospfv2-ext:log-adjacency-state-changes')) + if 'log_adjacency_changes' in ospf_dict: + ospf_dict['log_adjacency_changes'] = 'brief' if 'BRIEF' in ospf_dict['log_adjacency_changes'] else 'detail' + + if ospf_global.get("openconfig-ospfv2-ext:distance"): + distance_config = ospf_global.get("openconfig-ospfv2-ext:distance").get("config") + self.update_dict(ospf_dict, "all", distance_config.get("all"), "distance") + self.update_dict(ospf_dict, "external", distance_config.get("external"), "distance") + self.update_dict(ospf_dict, "inter_area", distance_config.get("inter-area"), "distance") + self.update_dict(ospf_dict, "intra_area", distance_config.get("intra-area"), "distance") + + return ospf_dict + + def get_ospf_timers_max_metric(self, ospf_global): + timers_dict, refresh_timer, max_metric_dict = {}, {}, {} + timers = ospf_global.get("timers", {}) + lsa_generation = timers.get("lsa-generation") + spf = timers.get("spf") + max_metric = timers.get("max-metric") + + if lsa_generation and lsa_generation.get("config"): + lsa_config = lsa_generation.get("config") + self.update_dict(timers_dict, "lsa_min_arrival", lsa_config.get("openconfig-ospfv2-ext:minimum-arrival")) + self.update_dict(timers_dict, "throttle_lsa_all", lsa_config.get("openconfig-ospfv2-ext:minimum-interval")) + self.update_dict(refresh_timer, "refresh_timer", lsa_config.get("openconfig-ospfv2-ext:refresh-timer")) + + if spf and spf.get("config"): + throttle_spf = {} + spf_config = spf.get("config") + self.update_dict(throttle_spf, "initial_hold_time", spf_config.get("initial-delay")) + self.update_dict(throttle_spf, "maximum_hold_time", spf_config.get("maximum-delay")) + self.update_dict(throttle_spf, "delay_time", spf_config.get("openconfig-ospfv2-ext:throttle-delay")) + + self.update_dict(timers_dict, "throttle_spf", throttle_spf) + + if max_metric and max_metric.get("config"): + max_metric_config = max_metric.get("config") + self.update_dict(max_metric_dict, "administrative", max_metric_config.get("openconfig-ospfv2-ext:administrative")) + self.update_dict(max_metric_dict, "external_lsa_all", max_metric_config.get("openconfig-ospfv2-ext:external-lsa-all")) + self.update_dict(max_metric_dict, "external_lsa_connected", max_metric_config.get("openconfig-ospfv2-ext:external-lsa-connected")) + self.update_dict(max_metric_dict, "on_startup", max_metric_config.get("openconfig-ospfv2-ext:on-startup")) + self.update_dict(max_metric_dict, "router_lsa_all", max_metric_config.get("openconfig-ospfv2-ext:router-lsa-all")) + self.update_dict(max_metric_dict, "router_lsa_stub", max_metric_config.get("openconfig-ospfv2-ext:router-lsa-stub")) + + return_dict = {} + self.update_dict(return_dict, "refresh_timer", refresh_timer.get('refresh_timer')) + self.update_dict(return_dict, "timers", timers_dict) + self.update_dict(return_dict, "max_metric", max_metric_dict) + + return return_dict + + def get_ospf_passive(self, ospf_passive_list): + return_dict = {} + passive_list, non_passive_list = [], [] + passive_interfaces = ospf_passive_list.get("passive-interface", []) + for passive_interface in passive_interfaces: + passive_dict = {} + non_passive_dict = {} + config = passive_interface.get("config") + if config: + address = config.get("address") + non_passive = config.get("non-passive") + sub_interface = config.get("subinterface") + intf_name = config.get("name") + if sub_interface and sub_interface != 0: + intf_name = "%s.%s" % (intf_name, sub_interface) + if non_passive is not None: + intf_exist = False + if non_passive: + for np_dict in non_passive_list: + if np_dict.get('interface') == intf_name: + np_dict.setdefault('addresses', []) + np_dict['addresses'].append(address) + intf_exist = True + if not intf_exist: + non_passive_dict['interface'] = intf_name + if address: + non_passive_dict.setdefault('addresses', []) + non_passive_dict['addresses'].append(address) + else: + for p_dict in passive_list: + if p_dict.get('interface') == intf_name: + p_dict.setdefault('addresses', []) + p_dict['addresses'].append(address) + intf_exist = True + if not intf_exist: + passive_dict['interface'] = intf_name + if address: + passive_dict.setdefault('addresses', []) + passive_dict['addresses'].append(address) + if non_passive_dict: + non_passive_list.append(non_passive_dict) + if passive_dict: + passive_list.append(passive_dict) + + self.update_dict(return_dict, 'passive_interfaces', passive_list) + self.update_dict(return_dict, 'non_passive_interfaces', non_passive_list) + + return return_dict + + def get_ospf_redistribute(self, ospf_redistribute): + return_dict = {} + redistribute_list = [] + protocol_map = { + "BGP": "bgp", + "KERNEL": "kernel", + "DIRECTLY_CONNECTED": "connected", + "STATIC": "static", + "DEFAULT_ROUTE": "default_route" + } + + for redistribute in ospf_redistribute.get("distribute-list", []): + redistribute_dict = {} + config = redistribute.get("config") + if config: + protocol = config.get("protocol").split(":")[1] + if protocol and protocol in protocol_map: + map_protocol = protocol_map[protocol] + if map_protocol == "default_route": + self.update_dict(redistribute_dict, 'always', config.get("always")) + self.update_dict(redistribute_dict, "metric", config.get("metric")) + self.update_dict(redistribute_dict, "route_map", config.get("route-map")) + self.update_dict(redistribute_dict, "metric_type", config.get("metric-type")) + if "metric_type" in redistribute_dict: + type_val = redistribute_dict['metric_type'].split(":")[1] + redistribute_dict['metric_type'] = 1 if type_val == 'TYPE_1' else 2 + redistribute_dict['protocol'] = map_protocol + if redistribute_dict: + redistribute_list.append(redistribute_dict) + self.update_dict(return_dict, 'redistribute', redistribute_list) + + return return_dict + + def get_ospf_graceful_restart(self, graceful_restart): + return_dict, helper_dict = {}, {} + advertise_router_id = [] + config = graceful_restart.get("config") + helpers = graceful_restart.get('openconfig-ospfv2-ext:helpers', {}) + if config: + self.update_dict(return_dict, 'enable', config.get("enabled")) + self.update_dict(return_dict, 'grace_period', config.get("openconfig-ospfv2-ext:grace-period")) + self.update_dict(helper_dict, 'enable', config.get("helper-only")) + self.update_dict(helper_dict, 'planned_only', config.get("openconfig-ospfv2-ext:planned-only")) + self.update_dict(helper_dict, 'strict_lsa_checking', config.get("openconfig-ospfv2-ext:strict-lsa-checking")) + self.update_dict(helper_dict, 'supported_grace_time', config.get("openconfig-ospfv2-ext:supported-grace-time")) + if helpers: + for helper in helpers.get('helper', []): + if helper.get('neighbour-id'): + advertise_router_id.append(helper.get('neighbour-id')) + self.update_dict(helper_dict, 'advertise_router_id', advertise_router_id) + self.update_dict(return_dict, 'helper', helper_dict) + + if return_dict: + return_dict = {'graceful_restart': return_dict} + + return return_dict + + def update_dict(self, dict, key, value, parent_key=None): + if value not in [None, {}, [], ()]: + if parent_key: + dict.setdefault(parent_key, {}) + dict[parent_key][key] = value + else: + dict[key] = value diff --git a/plugins/module_utils/network/sonic/facts/ospfv2_interfaces/__init__.py b/plugins/module_utils/network/sonic/facts/ospfv2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/ospfv2_interfaces/ospfv2_interfaces.py b/plugins/module_utils/network/sonic/facts/ospfv2_interfaces/ospfv2_interfaces.py new file mode 100644 index 000000000..5996956ac --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/ospfv2_interfaces/ospfv2_interfaces.py @@ -0,0 +1,194 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic ospfv2_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospfv2_interfaces.ospfv2_interfaces import Ospfv2_interfacesArgs + +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties_from_list +) +from ansible.module_utils.connection import ConnectionError + +DEFAULT_ADDRESS = '0.0.0.0' + + +class Ospfv2_interfacesFacts(object): + """ The sonic ospfv2_interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ospfv2_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ospfv2_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + all_ospfv2_interface_configs = {} + if not data: + all_ospfv2_interface_configs = self.get_ospfv2_interfaces() + + for ospfv2_interface_config in all_ospfv2_interface_configs: + if ospfv2_interface_config: + obj = self.render_config(self.generated_spec, ospfv2_interface_config) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('ospfv2_interfaces', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['ospfv2_interfaces'] = remove_empties_from_list(params['config']) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + return conf + + def get_ospfv2_interfaces(self): + """Get all OSPFv2 interfaces available in chassis""" + request = [{"path": "data/openconfig-interfaces:interfaces", "method": "GET"}] + + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + ospf_configs = [] + + if "openconfig-interfaces:interfaces" in response[0][1]: + interfaces = response[0][1].get("openconfig-interfaces:interfaces", {}) + if interfaces.get('interface'): + interfaces = interfaces['interface'] + for interface in interfaces: + intf_name = interface.get('name') + if intf_name == "eth0": + continue + + ospf = None + ospf_config = {} + + if interface.get('openconfig-vlan:routed-vlan'): + ospf = interface.get('openconfig-vlan:routed-vlan', {}) + else: + ospf = interface.get('subinterfaces', {}).get('subinterface', [{}])[0] + + if ospf: + ipv4 = ospf.get('openconfig-if-ip:ipv4', {}) + if ipv4: + ospf_int = ipv4.get('openconfig-ospfv2-ext:ospfv2', {}) + if ospf_int: + ospf_attributes = [] + for address in ospf_int.get('if-addresses', []): + attr = {} + addr = address.get('address') + bfd = address.get('enable-bfd') + if bfd: + cfg = bfd.get('config') + if cfg: + bfd_cfg = {} + self.update_dict(bfd_cfg, 'enable', cfg.get('enabled')) + self.update_dict(bfd_cfg, 'bfd_profile', cfg.get('bfd-profile')) + self.update_dict(ospf_config, 'bfd', bfd_cfg) + config = address.get('config') + md_authentications = address.get('md-authentications') + if config: + self.update_dict(attr, 'area_id', config.get('area-id')) + self.update_dict(attr, 'authentication_type', config.get('authentication-type')) + self.update_dict(attr, 'cost', config.get('metric')) + self.update_dict(attr, 'hello_interval', config.get('hello-interval')) + self.update_dict(attr, 'mtu_ignore', config.get('mtu-ignore')) + self.update_dict(attr, 'priority', config.get('priority')) + self.update_dict(attr, 'retransmit_interval', config.get('retransmission-interval')) + self.update_dict(attr, 'transmit_delay', config.get('transmit-delay')) + + if 'authentication-key' in config: + attr['authentication'] = {} + self.update_dict(attr['authentication'], 'password', config.get('authentication-key')) + self.update_dict(attr['authentication'], 'encrypted', config.get('authentication-key-encrypted')) + + if config.get('dead-interval-minimal'): + self.update_dict(attr, 'hello_multiplier', config.get('hello-multiplier')) + else: + self.update_dict(attr, 'dead_interval', config.get('dead-interval')) + + if 'network-type' in config: + network = "broadcast" if 'BROADCAST' in config['network-type'] else "point_to_point" + ospf_config['network'] = network + + if md_authentications: + md_keys = [] + for md_auth in md_authentications.get('md-authentication', []): + md_config = md_auth.get('config', {}) + if md_config: + md_key = {} + self.update_dict(md_key, 'key_id', md_config.get('authentication-key-id')) + self.update_dict(md_key, 'md5key', md_config.get('authentication-md5-key')) + self.update_dict(md_key, 'encrypted', md_config.get('authentication-key-encrypted')) + if md_key: + md_keys.append(md_key) + + self.update_dict(attr, 'md_authentication', md_keys) + if attr: + if addr != DEFAULT_ADDRESS: + attr['address'] = addr + ospf_attributes.append(attr) + + self.update_dict(ospf_config, 'ospf_attributes', ospf_attributes) + if ospf_config: + ospf_config['name'] = intf_name + ospf_configs.append(ospf_config) + + return ospf_configs + + def update_dict(self, dict, key, value, parent_key=None): + if value not in [None, {}, []]: + if parent_key: + dict.setdefault(parent_key, {})[key] = value + else: + dict[key] = value diff --git a/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py b/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py index 05e6d6188..2a63cf357 100644 --- a/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py +++ b/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py @@ -258,6 +258,10 @@ def get_route_map_set_bgp_policy_attr(self, set_bgp_policy, parsed_route_map_stm if weight: parsed_route_map_stmt_set['weight'] = weight + tag = set_bgp_policy_cfg.get('set-tag') + if tag: + parsed_route_map_stmt_set['tag'] = tag + @staticmethod def get_rmap_set_community(set_bgp_policy, parsed_route_map_stmt_set): '''Parse the "community" sub-section of the BGP policy "set" attribute diff --git a/plugins/module_utils/network/sonic/facts/system/system.py b/plugins/module_utils/network/sonic/facts/system/system.py index 781ce1dd1..6d2ac6fe3 100644 --- a/plugins/module_utils/network/sonic/facts/system/system.py +++ b/plugins/module_utils/network/sonic/facts/system/system.py @@ -56,21 +56,22 @@ def get_system(self): data = {} return data - def get_naming(self): - """Get interface_naming type available in chassis""" + def get_intf_naming_auto_breakout(self): + """Get interface_naming_mode and auto-breakout status available in chassis""" request = [{"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET}] try: response = edit_config(self._module, to_request(self._module, request)) except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) + data = {} if ('sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]): intf_data = response[0][1]['sonic-device-metadata:DEVICE_METADATA_LIST'] if 'intf_naming_mode' in intf_data[0]: if intf_data[0]['intf_naming_mode'] == 'standard-ext': intf_data[0]['intf_naming_mode'] = 'standard_extended' - data = intf_data[0] - else: - data = {} + data['intf_naming_mode'] = intf_data[0]['intf_naming_mode'] + if 'auto-breakout' in intf_data[0]: + data['auto-breakout'] = intf_data[0]['auto-breakout'] return data def get_anycast_addr(self): @@ -86,19 +87,36 @@ def get_anycast_addr(self): data = {} return data - def get_auto_breakout(self): - """Get auto-breakout status available in chassis""" - request = [{"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET}] + def get_load_share_hash_algo(self): + """Get load share hash algorithm""" + request = [{"path": "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config", "method": GET}] + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + if ('openconfig-loadshare-mode-ext:config' in response[0][1]): + data = response[0][1]['openconfig-loadshare-mode-ext:config'] + else: + data = {} + return data + + def get_auditd_rules(self): + """Get auditd rules configuration available in chassis""" + request = [{"path": "data/openconfig-system:system/openconfig-system-ext:auditd-system", "method": GET}] try: response = edit_config(self._module, to_request(self._module, request)) except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) data = {} - if ('sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]): - auto_breakout_data = response[0][1]['sonic-device-metadata:DEVICE_METADATA_LIST'] - if 'auto-breakout' in auto_breakout_data[0]: - auto_breakout_val = auto_breakout_data[0]['auto-breakout'] - data = {'auto-breakout': auto_breakout_val} + if response and response[0]: + if len(response[0]) > 1: + if ('openconfig-system-ext:auditd-system' in response[0][1]): + auditd_system_data = response[0][1]['openconfig-system-ext:auditd-system'] + if 'config' in auditd_system_data: + audit_rules_config = auditd_system_data['config'] + if 'audit-rules' in audit_rules_config: + audit_rules = audit_rules_config['audit-rules'] + data['audit-rules'] = audit_rules return data def populate_facts(self, connection, ansible_facts, data=None): @@ -111,21 +129,24 @@ def populate_facts(self, connection, ansible_facts, data=None): """ if not data: data = self.get_system() - intf_naming = self.get_naming() - if intf_naming: - data.update(intf_naming) + intf_naming_auto_breakout = self.get_intf_naming_auto_breakout() + if intf_naming_auto_breakout: + data.update(intf_naming_auto_breakout) anycast_addr = self.get_anycast_addr() if anycast_addr: data.update(anycast_addr) - auto_breakout = self.get_auto_breakout() - if auto_breakout: - data.update(auto_breakout) + load_share_hash_algo = self.get_load_share_hash_algo() + if load_share_hash_algo: + data.update(load_share_hash_algo) + auditd_rules = self.get_auditd_rules() + if auditd_rules: + data.update(auditd_rules) objs = [] objs = self.render_config(self.generated_spec, data) facts = {} if objs: params = utils.validate_config(self.argument_spec, {'config': objs}) - facts['system'] = params['config'] + facts['system'] = utils.remove_empties(params['config']) ansible_facts['ansible_network_resources'].update(facts) return ansible_facts @@ -161,4 +182,9 @@ def parse_sonic_system(self, spec, conf): config['anycast_address']['mac_address'] = conf['gwmac'] if ('auto-breakout' in conf) and (conf['auto-breakout']): config['auto_breakout'] = conf['auto-breakout'] + if ('algorithm' in conf) and (conf['algorithm']): + config['load_share_hash_algo'] = conf['algorithm'] + if ('audit-rules' in conf) and (conf['audit-rules']): + config['audit_rules'] = conf['audit-rules'] + return utils.remove_empties(config) diff --git a/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py b/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py index ac53415c7..12b066706 100644 --- a/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py +++ b/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py @@ -102,52 +102,83 @@ def get_vlan_mappings(self): vlan_mapping_configs = {} for interface in interfaces: response = self.get_port_mappings(interface) + if "openconfig-interfaces-ext:mapped-vlans" in response: vlan_list = response["openconfig-interfaces-ext:mapped-vlans"].get("mapped-vlan", {}) for vlan_mapping in vlan_list: vlan_mapping_dict = {} - vlan_mapping_dict["vlan_ids"] = [] - - tmp_dot1q_tunnel = (vlan_mapping - .get("egress-mapping", {}) - .get("config", {}) - .get("vlan-stack-action", "SWAP")) - if tmp_dot1q_tunnel == "SWAP": - vlan_mapping_dict["dot1q_tunnel"] = False - vlan_mapping_dict["inner_vlan"] = (vlan_mapping - .get("match", {}) - .get("double-tagged", {}) - .get("config", {}) - .get("inner-vlan-id", None)) - if vlan_mapping_dict["inner_vlan"]: - (vlan_mapping_dict["vlan_ids"] - .append(vlan_mapping.get("match", {}) - .get("double-tagged", {}) - .get("config", {}) - .get("outer-vlan-id", None))) - else: - (vlan_mapping_dict["vlan_ids"] - .append(vlan_mapping.get("match", {}) - .get("single-tagged", {}) - .get("config", {}) - .get("vlan-ids", None))) - if vlan_mapping_dict["vlan_ids"]: - vlan_mapping_dict["vlan_ids"][0] = vlan_mapping_dict["vlan_ids"][0][0] - else: - vlan_mapping_dict["dot1q_tunnel"] = True - tmp_vlan_ids = (vlan_mapping - .get("match", {}) - .get("single-tagged", {}) - .get("config", {}) - .get("vlan-ids", None)) - if tmp_vlan_ids: - vlan_mapping_dict["vlan_ids"].extend(tmp_vlan_ids[0].replace('..', '-').split(',')) vlan_mapping_dict["service_vlan"] = vlan_mapping.get("vlan-id", None) - vlan_mapping_dict["priority"] = (vlan_mapping - .get("egress-mapping", {}) - .get("config", {}) - .get("mapped-vlan-priority", None)) + + vs_action = (vlan_mapping + .get("egress-mapping", {}) + .get("config", {}) + .get("vlan-stack-action", "SWAP")) + + if vs_action == "SWAP": + vlan_trans_dict = dict() + m_tag = (vlan_mapping + .get("config", {}) + .get("multi-tag", False)) + if m_tag: + vlan_trans_dict["multi_tag"] = True + match = vlan_mapping.get("match", {}) + + if "match-single-tags" in match: + match_single_tags = (vlan_mapping + .get("match", {}) + .get("match-single-tags", {}) + .get("match-single-tag", [])) + ms_tags_list = [] + for ms_tag in match_single_tags: + ms_tag_dict = dict() + ms_tag_dict["outer_vlan"] = ms_tag["outer-vlan"] + ms_tag_dict["priority"] = (ms_tag + .get("config", {}) + .get("priority", None)) + + ms_tags_list.append(ms_tag_dict) + vlan_trans_dict["match_single_tags"] = ms_tags_list + + if 'match-double-tags' in match: + match_double_tags = (vlan_mapping + .get("match", {}) + .get("match-double-tags", {}) + .get("match-double-tag", [])) + md_tags_list = [] + for md_tag in match_double_tags: + md_tag_dict = dict() + md_tag_dict["inner_vlan"] = md_tag["inner-vlan"] + md_tag_dict["outer_vlan"] = md_tag["outer-vlan"] + md_tag_dict["priority"] = (md_tag + .get("config", {}) + .get("priority", None)) + + md_tags_list.append(md_tag_dict) + vlan_trans_dict["match_double_tags"] = md_tags_list + + if vlan_trans_dict: + vlan_mapping_dict["vlan_translation"] = vlan_trans_dict + else: + match = vlan_mapping.get("match", {}) + + if "single-tagged" in match: + st_vlan_ids = (vlan_mapping + .get("match", {}) + .get("single-tagged", {}) + .get("config", {}) + .get("vlan-ids", None)) + st_tagged_dict = dict() + if st_vlan_ids: + if isinstance(st_vlan_ids[0], str): + st_tagged_dict["vlan_ids"] = st_vlan_ids[0].replace('..', '-').split(',') + elif isinstance(st_vlan_ids[0], int): + st_tagged_dict["vlan_ids"] = [str(st_vlan_ids[0])] + st_tagged_dict["priority"] = (vlan_mapping + .get("egress-mapping", {}) + .get("config", {}) + .get("mapped-vlan-priority", None)) + vlan_mapping_dict["dot1q_tunnel"] = st_tagged_dict if interface["ifname"] in vlan_mapping_configs: vlan_mapping_configs[interface["ifname"]].append(vlan_mapping_dict) diff --git a/plugins/module_utils/network/sonic/facts/vrrp/__init__.py b/plugins/module_utils/network/sonic/facts/vrrp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/vrrp/vrrp.py b/plugins/module_utils/network/sonic/facts/vrrp/vrrp.py new file mode 100644 index 000000000..9e22621ca --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/vrrp/vrrp.py @@ -0,0 +1,193 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic vrrp fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils \ + import ( + remove_empties_from_list + ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.vrrp.vrrp import VrrpArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible.module_utils.connection import ConnectionError + +VRRP_ATTRIBUTES = { + 'virtual-router-id': 'virtual_router_id', + 'advertisement-interval': 'advertisement_interval', + 'preempt': 'preempt', + 'priority': 'priority', + 'openconfig-interfaces-ext:use-v2-checksum': 'use_v2_checksum', + 'openconfig-interfaces-ext:version': 'version', +} + + +class VrrpFacts(object): + """ The sonic vrrp fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = VrrpArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for vrrp + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if connection: # just for linting purposes, remove + pass + all_vrrp_configs = {} + + if not data: + all_vrrp_configs = self.get_vrrp() + + for vrrp_config in all_vrrp_configs: + obj = self.render_config(self.generated_spec, vrrp_config) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('vrrp', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['vrrp'] = remove_empties_from_list(params['config']) + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + return conf + + def get_vrrp(self): + """Get all VRRP/VRRP6 configurations available in chassis""" + request = [{'path': 'data/openconfig-interfaces:interfaces', 'method': 'GET'}] + + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + vrrp_configs = [] + + if 'openconfig-interfaces:interfaces' in response[0][1]: + interfaces = response[0][1].get('openconfig-interfaces:interfaces', {}) + if interfaces.get('interface'): + interfaces = interfaces['interface'] + for interface in interfaces: + intf_name = interface.get('name') + openconfig = None + if 'Eth' in intf_name or 'PortChannel' in intf_name: + sub_interface = interface.get('subinterfaces', {}) + sub_intf_list = sub_interface.get('subinterface', {}) + for sub_intf in sub_intf_list: + if sub_intf.get('index') != 0: + intf_name = intf_name + '.' + str(sub_intf.get('index')) + openconfig = sub_intf + if openconfig: + vrrp_intf_config = self.get_vrrp_from_interface(openconfig, intf_name) + if vrrp_intf_config: + vrrp_configs.append(vrrp_intf_config) + elif 'Vlan' in intf_name: + openconfig = interface.get('openconfig-vlan:routed-vlan') + if openconfig: + vrrp_intf_config = self.get_vrrp_from_interface(openconfig, intf_name) + if vrrp_intf_config: + vrrp_configs.append(vrrp_intf_config) + + return vrrp_configs + + def get_vrrp_from_interface(self, openconfig, intf_name): + vrrp = {} + ipv4_dict = openconfig.get('openconfig-if-ip:ipv4') + ipv6_dict = openconfig.get('openconfig-if-ip:ipv6') + ipv4_vrrp_list, ipv6_vrrp_list = [], [] + if ipv4_dict and ipv4_dict.get('addresses') and ipv4_dict['addresses'].get('address'): + ipv4_address_list = ipv4_dict['addresses']['address'] + for ipv4_addr in ipv4_address_list: + if ipv4_addr.get('vrrp') and ipv4_addr['vrrp'].get('vrrp-group'): + ipv4_list = self.get_vrrp_from_ip_dict(ipv4_addr['vrrp']['vrrp-group'], 'ipv4') + if ipv4_list: + ipv4_vrrp_list.extend(ipv4_list) + if ipv6_dict and ipv6_dict.get('addresses') and ipv6_dict['addresses'].get('address'): + ipv6_address_list = ipv6_dict['addresses']['address'] + for ipv6_addr in ipv6_address_list: + if ipv6_addr.get('vrrp') and ipv6_addr['vrrp'].get('vrrp-group'): + ipv6_list = self.get_vrrp_from_ip_dict(ipv6_addr['vrrp']['vrrp-group'], 'ipv6') + if ipv6_list: + ipv6_vrrp_list.extend(ipv6_list) + if ipv4_vrrp_list or ipv6_vrrp_list: + vrrp['group'] = [] + if ipv4_vrrp_list: + vrrp['group'].extend(ipv4_vrrp_list) + if ipv6_vrrp_list: + vrrp['group'].extend(ipv6_vrrp_list) + if vrrp: + vrrp['name'] = intf_name + return vrrp + + def get_vrrp_from_ip_dict(self, vrrp_group, afi): + vrrp_object = [] + for group in vrrp_group: + vrrp_dict = {} + track_interface = group.get('openconfig-interfaces-ext:vrrp-track') + track_intf_group = [] + config = group.get('config', []) + for cfg in config: + if cfg == 'virtual-address': + if config.get('virtual-address'): + vrrp_dict['virtual_address'] = [] + for address in config['virtual-address']: + if address: + vrrp_dict['virtual_address'].append({'address': address}) + else: + if cfg in VRRP_ATTRIBUTES: + vrrp_dict[VRRP_ATTRIBUTES[cfg]] = config[cfg] + if track_interface: + for track_intf in track_interface.get('vrrp-track-interface', []): + track_cfg = track_intf.get('config', None) + if track_cfg and 'track-intf' in track_cfg and 'priority-increment' in track_cfg: + track_intf_group.append({'interface': track_cfg['track-intf'], 'priority_increment': track_cfg['priority-increment']}) + if track_intf_group: + vrrp_dict['track_interface'] = track_intf_group + if vrrp_dict: + vrrp_dict['afi'] = afi + vrrp_object.append(vrrp_dict) + return vrrp_object diff --git a/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py b/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py index 5fe66daa4..30d92e7ec 100644 --- a/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py +++ b/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py @@ -191,10 +191,9 @@ def fill_vrf_map(self, vxlans, vxlan_vrf_list): continue matched_vtep = None - for each_vxlan in vxlans: - for each_vlan in each_vxlan.get('vlan_map', []): - if vni == each_vlan['vni']: - matched_vtep = each_vxlan + if vxlans: + # SONIC supports only one VxLan interface. + matched_vtep = vxlans[0] if matched_vtep: vni = int(each_vrf['vni']) diff --git a/plugins/module_utils/network/sonic/utils/bgp_utils.py b/plugins/module_utils/network/sonic/utils/bgp_utils.py index 9c2d18a52..cf22db5a6 100644 --- a/plugins/module_utils/network/sonic/utils/bgp_utils.py +++ b/plugins/module_utils/network/sonic/utils/bgp_utils.py @@ -28,11 +28,195 @@ 'openconfig-bgp-types:IPV6_UNICAST': 'ipv6_unicast', 'openconfig-bgp-types:L2VPN_EVPN': 'l2vpn_evpn', } + +AS_NOTATION_TYPES_MAP = { + 'ASDOT': 'asdot', + 'ASDOT_PLUS': 'asdot+', +} + +AS_NOTATION_TO_TYPES_MAP = { + 'asdot': 'ASDOT', + 'asdot+': 'ASDOT_PLUS', +} + GET = "get" network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance' protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp' +def to_bgp_as_notation_request_type(as_notation): + """Convert as_notation types to Openconfig As-dot enums""" + return AS_NOTATION_TO_TYPES_MAP.get(as_notation) + + +class BgpAsn(str): + """BgpAsn class to equate asdot+ and asplain""" + def __new__(cls, as_val): + if isinstance(as_val, str): + obj = super().__new__(cls, as_val) + obj.intval = int(as_val) if as_val.find('.') < 0 else (int(as_val.split('.')[0]) * 0x10000 + int(as_val.split('.')[1])) + return obj + if isinstance(as_val, int): + obj = super().__new__(cls, as_val) + obj.intval = as_val + else: + raise TypeError('Invalid BGP AS Number') + return obj + + def __eq__(self, other): + if isinstance(other, BgpAsn): + return self.intval == other.intval + if isinstance(other, str): + if len(other) == 0: + return False + if other.find('.') < 0: + if other.isdigit(): + return self.intval == int(other) + return False + return self.intval == (int(other.split('.')[0]) * 0x10000 + int(other.split('.')[1])) + if isinstance(other, int): + return self.intval == other + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def to_request_attr_fmt(self): + """Return asn according to openconfig model (original input: asdot(+) as string, asplain as integer)""" + return self.intval if self.__str__().find('.') < 0 else self.__str__() + + +def convert_bgp_asn(cfglist): + """Convert Bgp Asn values (int/str) to BgpAsn class """ + if not cfglist: + return + for bgp in cfglist: + if bgp.get('bgp_as'): + bgp['bgp_as'] = BgpAsn(bgp['bgp_as']) + if bgp.get('neighbors'): + for nbr in bgp['neighbors']: + if nbr.get('remote_as') and isinstance(nbr['remote_as'], dict): + if nbr['remote_as'].get('peer_as'): + nbr['remote_as']['peer_as'] = BgpAsn(nbr['remote_as']['peer_as']) + if nbr.get('local_as') and isinstance(nbr['local_as'], dict): + if nbr['local_as'].get('as'): + nbr['local_as']['as'] = BgpAsn(nbr['local_as']['as']) + if bgp.get('peer_group'): + for pgrp in bgp['peer_group']: + if pgrp.get('remote_as') and isinstance(pgrp['remote_as'], dict): + if pgrp['remote_as'].get('peer_as'): + pgrp['remote_as']['peer_as'] = BgpAsn(pgrp['remote_as']['peer_as']) + if pgrp.get('local_as') and isinstance(pgrp['local_as'], dict): + if pgrp['local_as'].get('as'): + pgrp['local_as']['as'] = BgpAsn(pgrp['local_as']['as']) + + +class BgpAsnStrList(str): + """BgpAsn String List class to equate string with asdot+ and asplain""" + def __new__(cls, as_val): + if isinstance(as_val, str): + if as_val.find('.') >= 0: + as_strlist = as_val.split(',') + for idx, asn in enumerate(as_strlist): + if asn.count('.') == 1: + as_strlist[idx] = str(int(asn.split('.')[0]) * 0x10000 + int(asn.split('.')[1])) + elif not asn.isdigit(): + raise TypeError('Invalid BGP AS Number List') + obj = super().__new__(cls, as_val) + obj.strvals = ','.join(as_strlist) + return obj + else: + for asn in as_val.split(','): + if not asn.isdigit(): + raise TypeError('Invalid BGP AS Number List') + obj = super().__new__(cls, as_val) + obj.strvals = as_val + return obj + raise TypeError('Invalid BGP AS Number List') + + def __eq__(self, other): + if isinstance(other, BgpAsnStrList): + return self.strvals == other.strvals + if isinstance(other, str): + if len(other) == 0: + return False + if other.find('.') < 0: + return self.strvals == other + o_strlist = other.split(',') + for idx, asn in enumerate(o_strlist): + if asn.find('.') >= 0: + o_strlist[idx] = str(int(asn.split('.')[0]) * 0x10000 + int(asn.split('.')[1])) + return self.strvals == ','.join(o_strlist) + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def to_request_attr_fmt(self): + """Return asn string list according to openconfig model (original input string)""" + return self.__str__() + + +class BgpAsnNN(str): + """BgpAsnNN class to equate ASN:NN with asdot+ and asplain""" + def __new__(cls, as_val): + if isinstance(as_val, str): + asn = as_val.split(':') + if len(asn) != 2 or not asn[1].isdigit(): + raise TypeError('Invalid ASN:NN_OR_IP-ADDRESS:NN') + dotcnt = asn[0].count('.') + if dotcnt == 1: + obj = super().__new__(cls, as_val) + obj.asnum_nn = str(int(asn[0].split('.')[0]) * 0x10000 + int(asn[0].split('.')[1])) + ':' + asn[1] + return obj + if (dotcnt == 0 and asn[0].isdigit) or dotcnt == 3: + # asplain asn:nn or 3-dots for IPv4:NN + obj = super().__new__(cls, as_val) + obj.asnum_nn = as_val + return obj + raise TypeError('Invalid ASN:NN_OR_IP-ADDRESS:NN') + + def __eq__(self, other): + if isinstance(other, BgpAsnNN): + return self.asnum_nn == other.asnum_nn + elif isinstance(other, str): + if len(other) == 0: + return False + asn = other.split(':') + if len(asn) != 2 or not asn[1].isdigit(): + return False + if asn[0].count('.') == 1: + return self.asnum_nn == str(int(asn[0].split('.')[0]) * 0x10000 + int(asn[0].split('.')[1])) + ':' + asn[1] + if asn[0].isdigit: + return self.asnum_nn == other + return False + + def __ne__(self, other): + return not self.__eq__(other) + + +def convert_routemap_bgp_asn(cfglist): + """Convert Routemaps Bgp Asn-list-string and ext-commnunity ASN:NN to BgpAsnStrList and BgpAsnNN class """ + if not cfglist: + return + for rtmap in cfglist: + if rtmap.get('set'): + if rtmap['set'].get('as_path_prepend'): + if isinstance(rtmap['set']['as_path_prepend'], str): + rtmap['set']['as_path_prepend'] = BgpAsnStrList(rtmap['set']['as_path_prepend']) + if rtmap['set'].get('extcommunity'): + for extcom_type in ['rt', 'soo']: + if rtmap['set']['extcommunity'].get(extcom_type): + for idx, asnn in enumerate(rtmap['set']['extcommunity'][extcom_type]): + rtmap['set']['extcommunity'][extcom_type][idx] = BgpAsnNN(asnn) + + +def to_extcom_str_list(asn_nn_list): + """Return BgpAsnNN list as list of strings (original input string)""" + return [extcm.__str__() for extcm in asn_nn_list] + + def get_all_vrfs(module): """Get all VRF configurations available in chassis""" all_vrfs = [] @@ -375,7 +559,10 @@ def get_from_params_map(params_map, data): if key == 'config': for k, v in val.items(): if k == config_key: - val_data = val[config_key] + if config_key == 'as-notation': + val_data = AS_NOTATION_TYPES_MAP.get(val[config_key]) + else: + val_data = val[config_key] ret_data.update({want_key: val_data}) if config_key == 'afi-safi-name': ret_data.pop(want_key) diff --git a/plugins/module_utils/network/sonic/utils/utils.py b/plugins/module_utils/network/sonic/utils/utils.py index 5a35d4b02..71f47c3d8 100644 --- a/plugins/module_utils/network/sonic/utils/utils.py +++ b/plugins/module_utils/network/sonic/utils/utils.py @@ -363,6 +363,28 @@ def remove_empties_from_list(config_list): return ret_config +def remove_none(config): + '''goes through nested dictionary items and removes any keys that have None as value. + enables using empty list/dict to specify clear everything for that section and differentiate this + 'clear everything' case from when no value was given + Note: This function is provided as an alternative to the "remove_empties" function in + ansible utils because the Ansible 'remove_empties' function will remove empty lists + and dicts as well as None''' + if isinstance(config, dict): + for k, v in list(config.items()): + if v is not None: + remove_none(v) + if v is None: + del config[k] + elif isinstance(config, list): + for item in list(config): + if item is not None: + remove_none(item) + if item is None: + config.remove(item) + return config + + def get_device_interface_naming_mode(module): intf_naming_mode = "" request = {"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET} diff --git a/plugins/modules/sonic_aaa.py b/plugins/modules/sonic_aaa.py index c17c0f711..129c63337 100644 --- a/plugins/modules/sonic_aaa.py +++ b/plugins/modules/sonic_aaa.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -35,224 +35,397 @@ module: sonic_aaa version_added: 1.1.0 notes: -- Tested against Enterprise SONiC Distribution by Dell Technologies. -- Supports C(check_mode). -author: Abirami N (@abirami-n) -short_description: Manage AAA and its parameters +- Tested against Enterprise SONiC Distribution by Dell Technologies +- Supports C(check_mode) +author: Shade Talabi (@stalabi1) +short_description: Manage AAA configuration on SONiC description: - - This module is used for configuration management of aaa parameters on devices running Enterprise SONiC. + - This module provides configuration management of AAA for devices running SONiC. options: config: description: - - Specifies the aaa related configurations + - AAA configuration + - For all lists in the module, the list items should be specified in order of desired priority. + - List items specified first have the highest priority. type: dict suboptions: authentication: description: - - Specifies the configurations required for aaa authentication + - AAA authentication configuration type: dict + version_added: 3.0.0 suboptions: - data: + auth_method: description: - - Specifies the data required for aaa authentication - type: dict - suboptions: - fail_through: - description: - - Specifies the state of failthrough - type: bool - local: - description: - - Enable or Disable local authentication - type: bool - group: - description: - - Specifies the method of aaa authentication - type: str - choices: - - ldap - - radius - - tacacs+ - + - Specifies the order of the methods in which to authenticate login + type: list + elements: str + choices: ['ldap', 'local', 'radius', 'tacacs+'] + console_auth_local : + description: + Enable/disable local authentication on console + type: bool + failthrough: + description: + - Enable/disable failthrough + type: bool + authorization: + description: + - AAA authorization configuration + type: dict + version_added: 3.0.0 + suboptions: + commands_auth_method: + description: + - Specifies the order of the methods in which to authorize commands + type: list + elements: str + choices: ['local', 'tacacs+'] + login_auth_method: + description: + - Specifies the order of the methods in which to authorize login + type: list + elements: str + choices: ['ldap', 'local'] + name_service: + description: + - AAA name-service configuration + type: dict + version_added: 3.0.0 + suboptions: + group: + description: + - Name-service source for group method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + netgroup: + description: + - Name-service source for netgroup method + type: list + elements: str + choices: ['ldap', 'local'] + passwd: + description: + - Name-service source for passwd method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + shadow: + description: + - Name-service source for shadow method + type: list + elements: str + choices: ['ldap', 'local', 'login'] + sudoers: + description: + - Name-service source for sudoers method + type: list + elements: str + choices: ['ldap', 'local'] state: description: - - Specifies the operation to be performed on the aaa parameters configured on the device. - - In case of merged, the input configuration will be merged with the existing aaa configuration on the device. - - In case of deleted the existing aaa configuration will be removed from the device. - - In case of replaced, the existing aaa configuration will be replaced with provided configuration. - - In case of overridden, the existing aaa configuration will be overridden with the provided configuration. - default: merged + - The state of the configuration after module completion choices: ['merged', 'deleted', 'overridden', 'replaced'] + default: merged type: str """ EXAMPLES = """ -# Using deleted +# Using Merged # # Before state: # ------------- # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : local +# sonic# show aaa +# (No AAA configuration present) -- name: Delete aaa configurations +- name: Merge AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - local: True - state: deleted + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + netgroup: + - local + passwd: + - login + shadow: + - ldap + sudoers: + - local + state: merged # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -# Using deleted +# Using Replaced # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : local +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Delete aaa configurations +- name: Replace AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: - state: deleted + authentication: + console_auth_local: True + failthrough: False + authorization: + commands_auth_method: + - local + name_service: + group: + - ldap + state: replaced # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : +# failthrough : False # login-method : +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap -# Using merged +# Using Overridden # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Merge aaa configurations +- name: Override AAA configuration dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - local: true - fail_through: true - state: merged + auth_method: + - tacacs+ + console_auth_local: True + failthrough: True + state: overridden # After state: # ------------ # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- # failthrough : True -# login-method : local +# login-method : tacacs+ +# console authentication : local -# Using replaced +# Using Deleted # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : local, radius +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Replace aaa configurations +- name: Delete AAA individual attributes dellemc.enterprise_sonic.sonic_aaa: config: authentication: - data: - group: ldap - fail_through: true - state: replaced + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + netgroup: + - local + passwd: + - login + shadow: + - ldap + sudoers: + - local + state: deleted # After state: # ------------ # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : local, ldap +# sonic# show aaa +# (No AAA configuration present) -# Using overridden +# Using Deleted # # Before state: # ------------- # -# do show aaa +# sonic# show aaa +# --------------------------------------------------------- # AAA Authentication Information # --------------------------------------------------------- -# failthrough : False -# login-method : local, radius +# failthrough : True +# login-method : local, ldap, radius, tacacs+ +# console authentication : local +# --------------------------------------------------------- +# AAA Authorization Information +# --------------------------------------------------------- +# login : local, ldap +# commands : local, tacacs+ +# --------------------------------------------------------- +# AAA Name-Service Information +# --------------------------------------------------------- +# group-method : ldap +# netgroup-method : local +# passwd-method : login +# shadow-method : ldap +# sudoers-method : local -- name: Override aaa configurations +- name: Delete all AAA configuration dellemc.enterprise_sonic.sonic_aaa: - config: - authentication: - data: - group: tacacs+ - fail_through: true - state: overridden + config: {} + state: deleted # After state: # ------------ # -# do show aaa -# AAA Authentication Information -# --------------------------------------------------------- -# failthrough : True -# login-method : tacacs+ - +# sonic# show aaa +# (No AAA configuration present) """ RETURN = """ before: - description: The configuration prior to the model invocation. + description: The configuration prior to the module invocation. returned: always type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. after: - description: The resulting configuration model invocation. + description: The resulting configuration module invocation. returned: when changed type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. after(generated): - description: The generated configuration model invocation. + description: The generated configuration module invocation. returned: when C(check_mode) type: list sample: > The configuration returned will always be in the same format - of the parameters above. + as the parameters above. commands: description: The set of commands pushed to the remote device. returned: always diff --git a/plugins/modules/sonic_bgp.py b/plugins/modules/sonic_bgp.py index 89942856e..bc714ed83 100644 --- a/plugins/modules/sonic_bgp.py +++ b/plugins/modules/sonic_bgp.py @@ -65,6 +65,12 @@ description: - Enables/disables logging neighbor up/down and reset reason. type: bool + as_notation: + description: + - Specify the AS number notation format + - Option supported on Enterprise-Sonic releases 4.4.0 and higher. + choices: [ 'asdot', 'asdot+' ] + type: str max_med: description: - Configure max med and its parameters diff --git a/plugins/modules/sonic_bgp_af.py b/plugins/modules/sonic_bgp_af.py index 4a4c2c14d..ea867bb1b 100644 --- a/plugins/modules/sonic_bgp_af.py +++ b/plugins/modules/sonic_bgp_af.py @@ -239,6 +239,30 @@ - Route-targets to be exported. type: list elements: str + aggregate_address_config: + description: + - Aggregate address configuration + version_added: 2.5.0 + type: list + elements: dict + suboptions: + prefix: + description: + - Aggregate address prefix + type: str + required: True + as_set: + description: + - Enables/disables generation of AS set path information + type: bool + policy_name: + description: + - Preconfigured routing policy (route map name) to be applied to aggregate network + type: str + summary_only: + description: + - Enables/disables restriction of route information included in updates + type: bool state: description: - Specifies the operation to be performed on the BGP_AF process configured on the device. @@ -266,6 +290,8 @@ # maximum-paths 1 # maximum-paths ibgp 1 # network 3.3.3.3/16 +# aggregate-address 1.1.1.1/1 +# aggregate-address 5.5.5.5/5 as-set summary-only route-map rmap-1 # dampening # import vrf route-map rmap-1 # import vrf default @@ -353,6 +379,12 @@ vrf_list: - default route_map: rmap-1 + aggregate_address_config: + - prefix: "1.1.1.1/1" + - prefix: "5.5.5.5/5" + as_set: True + policy_name: rmap-1 + summary_only: True state: deleted # After state: @@ -368,6 +400,7 @@ # maximum-paths 1 # maximum-paths ibgp 1 # network 3.3.3.3/16 +# aggregate-address 5.5.5.5/5 # dampening #! #router bgp 51 @@ -393,6 +426,7 @@ # maximum-paths 1 # maximum-paths ibgp 1 # network 3.3.3.3/16 +# aggregate-address 5.5.5.5/5 as-set summary-only route-map rmap-1 # dampening # import vrf route-map rmap-1 # import vrf default @@ -478,6 +512,11 @@ - 2.2.2.2/16 - 192.168.10.1/32 dampening: True + aggregate_address_config: + - prefix: 1.1.1.1/1 + as_set: True + policy_name: bb + summary_only: True - afi: ipv6 safi: unicast max_path: @@ -527,6 +566,7 @@ # address-family ipv4 unicast # network 2.2.2.2/16 # network 192.168.10.1/32 +# aggregate-address 1.1.1.1/1 as-set summary-only route-map bb # dampening # ! # address-family ipv6 unicast @@ -550,7 +590,6 @@ # rd 5.5.5.5:55 # route-target import 88:88 # route-target export 77:77 -# # Using replaced @@ -592,6 +631,7 @@ # maximum-paths ibgp 1 # network 2.2.2.2/16 # network 192.168.10.1/32 +# aggregate-address 5.5.5.5/5 as-set summary-only route-map bb # dampening # ! # address-family ipv6 unicast @@ -628,7 +668,7 @@ advertise_pip_ip: "3.3.3.3" advertise_pip_peer_ip: "4.4.4.4" advertise_svi_ip: True - advertise_all_vni: False + advertise_all_vni: True advertise_default_gw: False route_advertise_list: - advertise_afi: ipv4 @@ -657,6 +697,9 @@ - protocol: connected - protocol: ospf metric: 30 + aggregate-address-config: + - prefix: '5.5.5.5/5' + as_set: True - bgp_as: 51 vrf_name: VrfReg2 address_family: @@ -707,10 +750,15 @@ # maximum-paths ibgp 1 # network 2.2.2.2/16 # network 192.168.10.1/32 +# aggregate-address 5.5.5.5/5 as-set # dampening # ! +# address-family ipv6 unicast +# redistribute static route-map aa metric 26 +# maximum-paths 4 +# maximum-paths ibgp 5 +# ! # address-family l2vpn evpn -# advertise-all-vni # advertise-svi-ip # advertise ipv4 unicast route-map bb # rd 1.1.1.1:11 @@ -794,7 +842,7 @@ advertise_pip_ip: "3.3.3.3" advertise_pip_peer_ip: "4.4.4.4" advertise_svi_ip: True - advertise_all_vni: False + advertise_all_vni: True advertise_default_gw: False route_advertise_list: - advertise_afi: ipv4 @@ -823,6 +871,11 @@ - protocol: connected - protocol: ospf metric: 30 + aggregate_address_config: + - prefix: 4.4.4.4/4 + as_set: True + policy_name: bb + summary_only: True state: overridden # After state: @@ -846,6 +899,7 @@ # maximum-paths ibgp 1 # network 2.2.2.2/16 # network 192.168.10.1/32 +# aggregate-address 4.4.4.4/4 as-set summary-only route-map bb # dampening # ! # address-family l2vpn evpn diff --git a/plugins/modules/sonic_bgp_neighbors.py b/plugins/modules/sonic_bgp_neighbors.py index bf28caa43..f837ea6ac 100644 --- a/plugins/modules/sonic_bgp_neighbors.py +++ b/plugins/modules/sonic_bgp_neighbors.py @@ -76,7 +76,7 @@ description: - Specifies remote AS number. - The range is from 1 to 4294967295. - type: int + type: str peer_type: description: - Specifies the type of BGP peer. @@ -200,7 +200,7 @@ as: description: - Local autonomous system number. - type: int + type: str required: True no_prepend: description: @@ -217,8 +217,8 @@ passive: description: - Do not send open messages to this peer. + - Default value while adding a new peergroup is C(False). type: bool - default: False shutdown_msg: description: - Add a shutdown message. @@ -251,6 +251,7 @@ description: - Holds afi mode. type: str + required: True choices: - ipv4 - ipv6 @@ -345,7 +346,7 @@ description: - Specifies remote AS number. - The range is from 1 to 4294967295. - type: int + type: str peer_type: description: - Specifies the type of BGP peer. @@ -473,7 +474,7 @@ as: description: - Local autonomous system number. - type: int + type: str required: True no_prepend: description: @@ -490,8 +491,8 @@ passive: description: - Do not send open messages to this neighbor. + - Default value while adding a new neighbor is C(False). type: bool - default: False port: description: - Neighbor's BGP port. @@ -522,11 +523,15 @@ - Specifies the operation to be performed on the BGP process that is configured on the device. - In case of merged, the input configuration is merged with the existing BGP configuration on the device. - In case of deleted, the existing BGP configuration is removed from the device. + - In case of replaced, the existing BGP configuration will be replaced with the input BGP configuration on the device. + - In case of overridden, the existing BGP configuration will be overriden with the input BGP configuration on the device. default: merged type: str choices: - merged - deleted + - replaced + - overridden """ EXAMPLES = """ # Using deleted @@ -550,12 +555,17 @@ # neighbor 192.168.1.4 # ! # peer-group SP1 +# timers connect 30 +# advertisement-interval 0 # bfd # capability dynamic # ! # peer-group SP2 +# timers connect 30 +# advertisement-interval 0 # ! # + - name: Deletes all BGP neighbors dellemc.enterprise_sonic.sonic_bgp_neighbors: config: @@ -575,8 +585,9 @@ #router bgp 11 # network import-check # timers 60 180 -# ! +#! # + # Using merged # # Before state: @@ -700,6 +711,7 @@ v6only: true - neighbor: 192.168.1.4 state: merged + # # After state: # ------------ @@ -715,6 +727,7 @@ # peer-group SPINE1 # timers 15 30 # timers connect 25 +# advertisement-interval 0 # shutdown message msg1 # disable-connected-check # strict-capability-match @@ -724,6 +737,8 @@ # description "description 1" # ebgp-multihop 1 # remote-as 4 +# timers connect 30 +# advertisement-interval 0 # bfd check-control-plane-failure profile "profile 1" # update-source interface Ethernet4 # capability dynamic @@ -762,8 +777,10 @@ # ! # neighbor 192.168.1.4 #! -# router bgp 51 -# timers 60 180 +#router bgp 51 +# network import-check +# timers 60 180 +# ! # neighbor interface Eth1/2 # description "description 1" # shutdown message msg1 @@ -786,6 +803,7 @@ # network import-check # timers 60 180 # + # Using deleted # # Before state: @@ -802,6 +820,8 @@ # peer-group SPINE # bfd # remote-as 4 +# timers connect 30 +# advertisement-interval 0 # ! # neighbor interface Eth1/3 # peer-group SPINE @@ -816,12 +836,15 @@ #! #router bgp 11 # network import-check -# timers 60 18 +# timers 60 180 # ! # peer-group SP +# timers connect 30 +# advertisement-interval 0 # ! # neighbor interface Eth1/3 # + - name: "Deletes sonic_bgp_neighbors and peer-groups specific to vrfname" dellemc.enterprise_sonic.sonic_bgp_neighbors: config: @@ -842,12 +865,15 @@ # ! #router bgp 11 # network import-check -# timers 60 18 +# timers 60 180 # ! # peer-group SP +# timers connect 30 +# advertisement-interval 0 # ! # neighbor interface Eth1/3 # + # Using deleted # # Before state: @@ -860,6 +886,8 @@ # peer-group SPINE # bfd # remote-as 4 +# timers connect 30 +# advertisement-interval 0 # ! # neighbor interface Eth1/3 # peer-group SPINE @@ -967,6 +995,7 @@ v6only: true - neighbor: 192.168.1.4 state: deleted + # # After state: # ------------- @@ -976,14 +1005,20 @@ # timers 60 180 # ! # peer-group SPINE1 +# timers connect 30 +# advertisement-interval 0 # ! # peer-group SPINE +# timers connect 30 +# advertisement-interval 0 # ! # neighbor interface Eth1/3 # ! # neighbor interface Eth1/2 +# ! # neighbor 1.1.1.1 # + # Using merged # # Before state: @@ -1019,6 +1054,8 @@ # sonic# show running-configuration bgp peer-group vrf default # ! # peer-group SPINE +# timers connect 30 +# advertisement-interval 0 # ! # address-family ipv4 unicast # default-originate route-map rmap_reg1 @@ -1027,6 +1064,7 @@ # send-community both # maximum-prefix 1 80 warning-only # + # Using deleted # # Before state: @@ -1035,6 +1073,8 @@ # sonic# show running-configuration bgp peer-group vrf default # ! # peer-group SPINE +# timers connect 30 +# advertisement-interval 0 # ! # address-family ipv6 unicast # default-originate route-map rmap_reg2 @@ -1064,8 +1104,294 @@ prefix_list_out: p2 state: deleted +# After state: +# ------------ +# # sonic# show running-configuration bgp peer-group vrf default -# (No bgp peer-group configuration present) +# ! +# peer-group SPINE +# timers connect 30 +# advertisement-interval 0 +# ! +# address-family ipv6 unicast +# send-community both +# ! + +# +# Using replaced +# +# Before state: +# ------------ +#! +#router bgp 51 vrf VrfCheck2 +# network import-check +# timers 60 180 +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE3 +# bfd +# remote-as 4 +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# remote-as 10 +# timers 15 30 +# advertisement-interval 15 +# bfd +# capability extended-nexthop +# capability dynamic +# ! +# neighbor 192.168.1.4 +#! +#router bgp 51 +# network import-check +# timers 60 18 +# ! +# peer-group SP +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# + +- name: "Replaces peer-groups specific to vrfname" + dellemc.enterprise_sonic.sonic_bgp_neighbors: + config: + - bgp_as: 51 + vrf_name: VrfReg1 + peer_group: + - name: SPINE3 + remote_as: + peer_type: internal + - name: SPINE4 + address_family: + afis: + - afi: ipv4 + safi: unicast + allowas_in: + origin: true + state: replaced + +# After state: +# ------------ +#! +#router bgp 51 vrf VrfCheck2 +# network import-check +# timers 60 180 +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE3 +# remote-as internal +# timers connect 30 +# advertisement-interval 0 +# ! +# peer-group SPINE4 +# timers connect 30 +# advertisement-interval 0 +# ! +# address-family ipv4 unicast +# allowas-in origin +# send-community both +#! +# neighbor interface Eth1/3 +# remote-as 10 +# timers 15 30 +# advertisement-interval 15 +# bfd +# capability extended-nexthop +# capability dynamic +# ! +# neighbor 192.168.1.4 +#! +#router bgp 51 +# network import-check +# timers 60 18 +# ! +# peer-group SP +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# +# + +# Using replaced +# +# Before state: +# ------------ +#! +#router bgp 51 vrf VrfCheck2 +# network import-check +# timers 60 180 +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE +# bfd +# remote-as 4 +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor 192.168.1.1 +# peer-group SPINE +# remote-as 10 +# timers 15 30 +# advertisement-interval 15 +# bfd +# capability extended-nexthop +# capability dynamic +# ! +# neighbor 192.168.1.4 +#! +#router bgp 51 +# network import-check +# timers 60 18 +# ! +# peer-group SP +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# + +- name: "Replaces sonic_bgp_neighbors specific to vrfname" + dellemc.enterprise_sonic.sonic_bgp_neighbors: + config: + - bgp_as: 51 + vrf_name: VrfReg1 + neighbors: + - neighbor: 192.168.1.1 + bfd: + enabled: true + capability: + extended_nexthop: true + dynamic: true + state: replaced + +# After state: +# ------------ +#! +#router bgp 51 vrf VrfCheck2 +# network import-check +# timers 60 180 +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE +# bfd +# remote-as 4 +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor 192.168.1.1 +# bfd +# capability extended-nexthop +# capability dynamic +# ! +# neighbor 192.168.1.4 +#! +#router bgp 51 +# network import-check +# timers 60 18 +# ! +# peer-group SP +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# + +# Using overridden +# +# Before state: +# ------------ +#! +#router bgp 51 vrf VrfCheck2 +# network import-check +# timers 60 180 +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE +# bfd +# remote-as 4 +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# peer-group SPINE +# remote-as 10 +# timers 15 30 +# advertisement-interval 15 +# bfd +# capability extended-nexthop +# capability dynamic +# ! +# neighbor 192.168.1.4 +#! +#router bgp 51 +# network import-check +# timers 60 18 +# ! +# peer-group SP +# timers connect 30 +# advertisement-interval 0 +# ! +# neighbor interface Eth1/3 +# + +- name: "Override sonic_bgp_neighbors and peer-groups specific to vrfname" + dellemc.enterprise_sonic.sonic_bgp_neighbors: + config: + - bgp_as: 51 + vrf_name: VrfReg1 + peer_group: + - name: SPINE3 + remote_as: + peer_type: internal + - name: SPINE4 + address_family: + afis: + - afi: ipv4 + safi: unicast + allowas_in: + origin: true + state: overridden + +# After state: +# ------------ +#! +#router bgp 51 vrf VrfReg1 +# network import-check +# timers 60 180 +# ! +# peer-group SPINE3 +# remote-as internal +# timers connect 30 +# advertisement-interval 0 +# ! +# peer-group SPINE4 +# timers connect 30 +# advertisement-interval 0 +# ! +# address-family ipv4 unicast +# allowas-in origin +# send-community both +#! +# """ RETURN = """ before: diff --git a/plugins/modules/sonic_facts.py b/plugins/modules/sonic_facts.py index b21759531..7e15383f2 100644 --- a/plugins/modules/sonic_facts.py +++ b/plugins/modules/sonic_facts.py @@ -65,15 +65,19 @@ - bgp_as_paths - bgp_communities - bgp_ext_communities + - ospfv2_interfaces + - ospfv2 - mclag - prefix_lists - vlan_mapping - vrfs + - vrrp - vxlans - users - system - port_breakout - aaa + - ldap - tacacs_server - radius_server - static_routes @@ -107,6 +111,8 @@ - pim_global - pim_interfaces - login_lockout + - mgmt_servers + - ospf_area """ EXAMPLES = """ diff --git a/plugins/modules/sonic_l3_interfaces.py b/plugins/modules/sonic_l3_interfaces.py index 27a89beaf..807dc0121 100644 --- a/plugins/modules/sonic_l3_interfaces.py +++ b/plugins/modules/sonic_l3_interfaces.py @@ -96,10 +96,30 @@ - IPv6 address to be set in the address format is / for example, 2001:db8:2201:1::1/64. type: str + eui64: + description: + - Flag to indicate whether it is eui64 address + version_added: 2.5.0 + type: bool + default: 'False' enabled: description: - enabled flag of the ipv6. type: bool + autoconf: + description: + - autoconfiguration flag + version_added: 2.5.0 + type: bool + dad: + description: + - IPv6 nd dad related configs. + version_added: 2.5.0 + type: str + choices: + - ENABLE + - DISABLE + - DISABLE_IPV6_ON_FAILURE state: description: - The state of the configuration after module completion. @@ -128,7 +148,10 @@ # ip address 84.1.1.1/16 secondary # ipv6 address 83::1/16 # ipv6 address 84::1/16 +# ipv6 address 85::/64 eui-64 # ipv6 enable +# ipv6 address autoconfig +# ipv6 nd dad enable #! #interface Ethernet24 # mtu 9100 @@ -147,7 +170,7 @@ #! # # -- name: delete one l3 interface. +- name: delete l3 interface attributes dellemc.enterprise_sonic.sonic_l3_interfaces: config: - name: Ethernet20 @@ -155,6 +178,9 @@ addresses: - address: 83.1.1.1/16 - address: 84.1.1.1/16 + ipv6: + addresses: + - address: 85::/64 - name: Ethernet24 ipv6: enabled: true @@ -165,7 +191,7 @@ anycast_addresses: - 11.12.13.14/12 state: deleted - +# # After state: # ------------ # @@ -178,6 +204,8 @@ # ipv6 address 83::1/16 # ipv6 address 84::1/16 # ipv6 enable +# ipv6 address autoconfig +# ipv6 nd dad enable #! #interface Ethernet24 # mtu 9100 @@ -208,7 +236,10 @@ # ip address 84.1.1.1/16 secondary # ipv6 address 83::1/16 # ipv6 address 84::1/16 +# ipv6 address 85::/64 eui-64 # ipv6 enable +# ipv6 address autoconfig +# ipv6 nd dad enable #! #interface Ethernet24 # mtu 9100 @@ -281,10 +312,13 @@ secondary: True ipv6: enabled: true + dad: ENABLE + autoconf: true addresses: - address: 83::1/16 - address: 84::1/16 - secondary: True + - address: 85::/64 + eui64: True - name: Ethernet24 ipv4: addresses: @@ -314,7 +348,10 @@ # ip address 84.1.1.1/16 secondary # ipv6 address 83::1/16 # ipv6 address 84::1/16 +# ipv6 address 85::/64 eui-64 # ipv6 enable +# ipv6 address autoconfig +# ipv6 nd dad enable #! #interface Ethernet24 # mtu 9100 @@ -458,7 +495,10 @@ # ip address 84.1.1.1/16 secondary # ipv6 address 83::1/16 # ipv6 address 84::1/16 +# ipv6 address 85::/64 eui-64 # ipv6 enable +# ipv6 address autoconfig +# ipv6 nd dad enable #! #interface Ethernet24 # mtu 9100 diff --git a/plugins/modules/sonic_lag_interfaces.py b/plugins/modules/sonic_lag_interfaces.py index b023faaff..c676fec81 100644 --- a/plugins/modules/sonic_lag_interfaces.py +++ b/plugins/modules/sonic_lag_interfaces.py @@ -59,7 +59,8 @@ type: dict suboptions: interfaces: - description: The list of interfaces that are part of the group. + description: + - The list of interfaces that are part of the group. type: list elements: dict suboptions: @@ -74,6 +75,34 @@ choices: - static - lacp + ethernet_segment: + description: + - Specifies Ethernet segment. + version_added: 2.5.0 + type: dict + suboptions: + esi_type: + description: + - Specifies type of Ethernet Segment Identifier. + esi_type and esi can not be deleted separately. + If both esi and df_preference are not present, + deleted state will delete whole ethernet segment. + required: True + type: str + choices: + - auto_lacp + - auto_system_mac + - ethernet_segment_id + esi: + description: + - Specifies value of Ethernet Segment Identifier. + Only "AUTO" is supported for auto_lacp and auto_system_mac. + type: str + df_preference: + description: + - The preference for Designated Forwarder election method. + The range of df_preference value is from 1 to 65535. + type: int state: description: - The state that the configuration should be left in. @@ -96,10 +125,7 @@ # speed 100000 # no shutdown # ! -# interface Eth1/15 -# channel-group 12 -# mtu 9100 -# speed 100000 +# interface PortChannel10 # no shutdown # - name: Merges provided configuration with device configuration @@ -109,6 +135,13 @@ members: interfaces: - member: Eth1/10 + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 + - name: PortChannel12 + members: + interfaces: + - member: Eth1/15 state: merged # # After state: @@ -125,6 +158,16 @@ # mtu 9100 # speed 100000 # no shutdown +# ! +# interface PortChannel10 +# no shutdown +# ! +# evpn ethernet-segment auto-lacp +# df-preference 2222 +# ! +# interface PortChannel12 +# no shutdown +# # # Using replaced # @@ -136,33 +179,42 @@ # mtu 9100 # speed 100000 # no shutdown -# -# interface Eth1/6 -# channel-group 20 -# mtu 9100 -# speed 100000 -# no shutdown -# +# ! # interface Eth1/7 # no channel-group # mtu 9100 # speed 100000 # no shutdown +# ! +# interface PortChannel10 +# no shutdown +# ! +# evpn ethernet-segment auto-lacp +# df-preference 2222 # - name: Replace device configuration of specified LAG attributes dellemc.enterprise_sonic.sonic_lag_interfaces: config: + - name: PortChannel20 + members: + interfaces: + - member: Eth1/6 + ethernet_segment: + esi_type: auto_system_mac + df_preference: 6666 - name: PortChannel10 members: interfaces: - member: Eth1/7 + ethernet_segment: + esi_type: auto_system_mac + df_preference: 3333 state: replaced # # After state: # ------------ # # interface Eth1/5 -# no channel-group # mtu 9100 # speed 100000 # no shutdown @@ -179,6 +231,18 @@ # speed 100000 # no shutdown # +# interface PortChannel10 +# no shutdown +# ! +# evpn ethernet-segment auto-system-mac +# df-preference 3333 +# +# interface PortChanne20 +# no shutdown +# ! +# evpn ethernet-segment auto-system-mac +# df-preference 6666 +# # Using overridden # # Before state: @@ -196,11 +260,11 @@ # speed 100000 # no shutdown # -# interface Eth1/7 -# channel-group 2 -# mtu 9100 -# speed 100000 +# interface PortChannel10 # no shutdown +# ! +# evpn ethernet-segment auto-system-mac +# df-preference 2222 # - name: Override device configuration of all LAG attributes dellemc.enterprise_sonic.sonic_lag_interfaces: @@ -209,12 +273,15 @@ members: interfaces: - member: Eth1/6 + ethernet_segment: + esi_type: auto_lacp + df_preference: 3333 state: overridden # # After state: # ------------ +# # interface Eth1/5 -# no channel-group # mtu 9100 # speed 100000 # no shutdown @@ -225,64 +292,79 @@ # speed 100000 # no shutdown # -# interface Eth1/7 -# no channel-group -# mtu 9100 -# speed 100000 -# no shutdown +# interface PortChannel20 +# no shutdown +# ! +# evpn ethernet-segment auto-lacp +# df-preference 3333 # # Using deleted # # Before state: # ------------- -# interface PortChannel10 +# interface PortChannel 10 +# no shutdown +# ! +# evpn ethernet-segment auto-lacp +# df-preference 2222 +# ! +# interface PortChannel 12 # ! # interface Eth1/10 # channel-group 10 # mtu 9100 # speed 100000 # no shutdown +# ! +# interface Eth1/15 +# channel-group 12 +# mtu 9100 +# speed 100000 +# no shutdown # -- name: Deletes LAG attributes of a given interface, This does not delete the port-channel itself +- name: Deletes all LAGs and LAG attributes of all interfaces dellemc.enterprise_sonic.sonic_lag_interfaces: config: - - name: PortChannel10 - members: - interfaces: state: deleted # # After state: -# ------------ -# interface PortChannel10 -# ! +# ------------- +# # interface Eth1/10 # mtu 9100 # speed 100000 # no shutdown +# ! +# interface Eth1/15 +# mtu 9100 +# speed 100000 +# no shutdown # # Using deleted # # Before state: # ------------- -# interface PortChannel 10 -# ! -# interface PortChannel 12 -# ! # interface Eth1/10 # channel-group 10 # mtu 9100 # speed 100000 # no shutdown # ! -# interface Eth1/15 -# channel-group 12 -# mtu 9100 -# speed 100000 +# interface PortChannel10 # no shutdown +# ! +# evpn ethernet-segment auto-lacp +# df-preference 2222 # -- name: Deletes all LAGs and LAG attributes of all interfaces - dellemc.enterprise_sonic.sonic_lag_interfaces: +- name: Deletes some LAGs and LAG attributes. + sonic_lag_interfaces: config: + - name: PortChannel10 + members: + interfaces: + - member: Eth1/10 + ethernet_segment: + esi_type: auto_lacp state: deleted # # After state: @@ -293,11 +375,9 @@ # speed 100000 # no shutdown # ! -# interface Eth1/15 -# mtu 9100 -# speed 100000 +# interface PortChannel10 # no shutdown -# +# ! # """ RETURN = """ diff --git a/plugins/modules/sonic_ldap.py b/plugins/modules/sonic_ldap.py new file mode 100644 index 000000000..ae05d7cbb --- /dev/null +++ b/plugins/modules/sonic_ldap.py @@ -0,0 +1,743 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_ldap +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: sonic_ldap +author: 'Santhosh Kumar T(@santhosh-kt)' +version_added: '2.5.0' +notes: +- Supports C(check_mode). +short_description: Configure global LDAP server settings on SONiC. +description: + - This module provides configuration management of global LDAP server parameters on devices running SONiC. + - Configure VRF instance before configuring VRF to be used for LDAP server connection. +options: + config: + description: + - Specifies the LDAP server related configuration. + type: list + elements: dict + suboptions: + name: + description: + - Specifies the LDAP type. + type: str + choices: + - global + - nss + - pam + - sudo + required: true + base: + description: + - Configure base distinguished name. + type: str + bind_timelimit: + description: + - Configure connect time limit (0 to 65535). + type: int + binddn: + description: + - Configure distinguished name to bind. + type: str + bindpw: + description: + - Configure credentials to bind + type: dict + suboptions: + pwd: + description: + - Authentication password for the bind. + type: str + required: true + encrypted: + description: + - Indicates whether the password is encrypted text. + type: bool + servers: + description: + - Configure host name or IP address for a LDAP server. + - Applicable only for global. + type: list + elements: dict + suboptions: + address: + description: + - Hostname or IP address of LDAP server. + type: str + required: true + port: + description: + - Configure server port number (1 to 65535). + type: int + priority: + description: + - Configure priority (1 to 99). + type: int + retry: + description: + - Configure retransmit attempt (0 to 10). + type: int + ssl: + description: + - Configure TLS configuration. + type: str + choices: + - "on" + - "off" + - "start_tls" + server_type: + description: + - Configure server type. + type: str + choices: + - all + - nss + - sudo + - pam + - nss_sudo + - nss_pam + - sudo_pam + idle_timelimit: + description: + - Configure NSS idle time limit (0 to 65535). + - Applicable only for global and nss. + type: int + map: + description: + - Configure LDAP server for map. + - Applicable only for global. + type: dict + suboptions: + attribute: + description: + - Configure attribute map. + - I(from) and I(to) are required together. + type: list + elements: dict + suboptions: + from: + description: + - Configure attribute map key. + type: str + to: + description: + - Configure attribute map value. + type: str + default_attribute: + description: + - Configure default attribute map. + - I(from) and I(to) are required together. + type: list + elements: dict + suboptions: + from: + description: + - Configure default attribute map key. + type: str + to: + description: + - Configure default attribute map value. + type: str + map_remote_groups_to_sonic_roles: + description: + - Configure mapping for remote groups to sonic roles. + - I(remote_group) and I(sonic_roles) are required together. + type: list + elements: dict + suboptions: + remote_group: + description: + - Map remote groups to SONiC roles. + type: str + sonic_roles: + description: + - Configure SONiC roles. + type: list + elements: str + choices: + - admin + - operator + - netadmin + - secadmin + objectclass: + description: + - Configure Objectclass map. + - I(from) and I(to) are required together. + type: list + elements: dict + suboptions: + from: + description: + - Configure Objectclass map key. + type: str + to: + description: + - Configure Objectclass map value. + type: str + override_attribute: + description: + - Configure override attribute map. + - I(from) and I(to) are required together. + type: list + elements: dict + suboptions: + from: + description: + - Configure override attribute map key. + type: str + to: + description: + - Configure override attribute map value. + type: str + nss_base_group: + description: + - Configure NSS search base for group map. + - Applicable only for global and nss. + type: str + nss_base_netgroup: + description: + - Configure NSS search base for netgroup map. + - Applicable only for global and nss. + type: str + nss_base_passwd: + description: + - Configure NSS search base for passwd map. + - Applicable only for global, nss and pam. + type: str + nss_base_shadow: + description: + - Configure NSS search base for shadow map. + - Applicable only for global and nss. + type: str + nss_base_sudoers: + description: + - Configure NSS search base for sudoers map. + - Applicable only for global and nss. + type: str + nss_initgroups_ignoreusers: + description: + - Configure NSS init groups ignore users. + - Applicable only for global and nss. + type: str + nss_skipmembers: + description: + - Configure NSS skipmembers + type: bool + pam_filter: + description: + - Configure PAM filter. + - Applicable only for global and pam. + type: str + pam_group_dn: + description: + - Configure PAM Group Distinguished name. + - Applicable only for global and pam. + type: str + pam_login_attribute: + description: + - Configure PAM Login attribute. + - Applicable only for global and pam. + type: str + pam_member_attribute: + description: + - Configure PAM Member attribute. + - Applicable only for global and pam. + type: str + port: + description: + - Configure server port (1 to 65535). + type: int + retry: + description: + - Configure retransmit attempt (0 to 10). + type: int + scope: + description: + - Configure the search scope. + - Applicable only for global, nss and pam. + type: str + choices: + - sub + - one + - base + source_interface: + description: + - Configure source interface to be used as source IP for the LDAP packets. + - Applicable only for global. + - Full name of the Layer 3 interface, i.e. Eth1/1. + type: str + ssl: + description: + - Configure TLS configuration. + type: str + choices: + - "on" + - "off" + - "start_tls" + sudoers_base: + description: + - Configure sudo base distinguished name for queries. + - Applicable only for global and sudo. + type: str + sudoers_search_filter: + description: + - Configure sudo search filter for queries. + - Applicable only for global and sudo. + type: str + timelimit: + description: + - Configure search time limit (1 to 65535). + type: int + version: + description: + - Configure LDAP version 2 or 3. + type: int + choices: + - 2 + - 3 + vrf: + description: + - Configure VRF to be used for LDAP server connection. + - Applicable only for global. + type: str + state: + description: + - Specifies the operation to be performed on the LDAP server configured on the device. + - In case of merged, the input configuration will be merged with the existing LDAP server configuration on the device. + - In case of deleted, the existing LDAP server configuration will be removed from the device. + - In case of overridden, all the existing LDAP server configuration will be deleted and the specified input configuration will be installed. + - In case of replaced, the existing LDAP server configuration on the device will be replaced by the configuration in the playbook + for each LDAP server group configured by the playbook. + default: merged + choices: ['merged', 'deleted', 'replaced', 'overridden'] + type: str +""" +EXAMPLES = """ +# Using deleted +# +# Before State: +# ------------- +# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server nss-initgroups-ignoreusers username1 +#ldap-server nss scope sub +#ldap-server nss timelimit 15 +#ldap-server nss idle-timelimit 25 +#ldap-server nss nss-base-group group1 +#ldap-server nss nss-base-sudoers sudo1 +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server sudo retry 10 +#ldap-server sudo ssl start_tls +#ldap-server sudo bind-timelimit 15 +#ldap-server vrf Vrf_1 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server host example.com priority 10 ssl off +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#sonic# + + - name: Delete the LDAP server configurations + sonic_ldap: + config: + - name: "global" + servers: + - address: "example.com" + vrf: "Vrf_1" + - name: "nss" + idle_timelimit: 25 + scope: "sub" + - name: "sudo" + state: deleted + +# After State: +# ------------ +# +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server nss-initgroups-ignoreusers username1 +#ldap-server nss timelimit 15 +#ldap-server nss nss-base-group group1 +#ldap-server nss nss-base-sudoers sudo1 +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#sonic# + + +# Using merged +# +# Before State: +# ------------- +# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#! +#sonic# show running-configuration | grep ldap +#sonic# + + - name: Add the LDAP server configurations + sonic_ldap: + config: + - name: "global" + servers: + - address: "example.com" + priority: 10 + ssl: on + - address: "10.10.10.1" + priority: 5 + port: 1550 + port: 389 + version: 2 + nss_base_passwd: password + - name: "pam" + base: "admin" + binddn: "CN=example.com" + pam_login_attribute: "loginattrstring" + - name: "sudo" + bind_timelimit: 20 + retry: 10 + state: merged + +# After State: +# ------------ +# +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server sudo retry 10 +#ldap-server sudo bind-timelimit 20 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host example.com priority 10 ssl on +#sonic# + + +# Using merged +# +# Before State: +# ------------- +# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#! +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server sudo retry 10 +#ldap-server sudo bind-timelimit 20 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host example.com priority 10 ssl on +#sonic# + + - name: Add the LDAP server configurations + sonic_ldap: + config: + - name: "global" + servers: + - address: "example.com" + ssl: off + - address: "20.20.20.10" + retry: 1 + nss_base_passwd: password + pam_login_attribute: "globallogin" + nss_initgroups_ignoreusers: "username1" + vrf: "Vrf_1" + map: + default_attribute: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + objectclass: + - from: "attr1" + to: "attr3" + map_remote_groups_to_sonic_roles: + - remote_group: "group1" + sonic_roles: + - admin + - operator + - name: "nss" + nss_base_netgroup: "group1" + idle_timelimit: 25 + timelimit: 15 + scope: "sub" + nss_base_sudoers: "sudo1" + - name: "sudo" + bind_timelimit: 15 + ssl: "start_tls" + state: merged + +# After State: +# ------------ +# +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server pam-login-attribute globallogin +#ldap-server nss-initgroups-ignoreusers username1 +#ldap-server nss scope sub +#ldap-server nss timelimit 15 +#ldap-server nss idle-timelimit 25 +#ldap-server nss nss-base-group group1 +#ldap-server nss nss-base-sudoers sudo1 +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server sudo retry 10 +#ldap-server sudo ssl start_tls +#ldap-server sudo bind-timelimit 15 +#ldap-server vrf Vrf_1 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server host example.com priority 10 ssl off +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#ldap-server map remote-groups-override-to-sonic-roles group1 to admin,operator +#sonic# + + +# Using replaced +# +# Before State: +# ------------- +# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss-base-passwd password +#ldap-server nss-initgroups-ignoreusers username1 +#ldap-server nss idle-timelimit 25 +#ldap-server nss nss-base-group group1 +#ldap-server nss nss-base-sudoers sudo1 +#ldap-server pam base admin +#ldap-server pam binddn CN=example.com +#ldap-server pam pam-login-attribute loginattrstring +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#sonic# + + - name: Replace the LDAP server configurations + sonic_ldap: + config: + - name: "nss" + scope: "one" + bindpw: + pwd: "password" + - name: "pam" + version: 3 + port: 2000 + timelimit: 20 + pam_group_dn: "DNAME" + - name: "sudo" + sudoers_search_filter: "filter1" + base: "base_name" + version: 3 + state: replaced + +# After State: +# ------------ +# +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss scope one +#ldap-server nss bindpw U2FsdGVkX1+t8PR9IIi+qjZpYoNwjmd78D1WDBdkLxs= encrypted +#ldap-server pam version 3 +#ldap-server pam port 2000 +#ldap-server pam timelimit 20 +#ldap-server pam pam-group-dn DNAME +#ldap-server sudo version 3 +#ldap-server sudo base base_name +#ldap-server sudo sudoers-search-filter filter1 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#sonic# + + +# Using overridden +# +# Before State: +# ------------- +# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration | grep ldap +#ldap-server port 389 +#ldap-server version 2 +#ldap-server nss scope one +#ldap-server nss bindpw U2FsdGVkX1+t8PR9IIi+qjZpYoNwjmd78D1WDBdkLxs= encrypted +#ldap-server pam version 3 +#ldap-server pam port 2000 +#ldap-server pam timelimit 20 +#ldap-server pam pam-group-dn DNAME +#ldap-server sudo version 3 +#ldap-server sudo base base_name +#ldap-server sudo sudoers-search-filter filter1 +#ldap-server host 10.10.10.1 port 1550 priority 5 +#ldap-server host 20.20.20.10 retry 1 +#ldap-server map default-attribute-value attr1 to attr2 +#ldap-server map default-attribute-value attr3 to attr4 +#ldap-server map objectclass attr1 to attr3 +#sonic# + + - name: Override the LDAP server configurations + sonic_ldap: + config: + - name: "global" + source_interface: "Eth1/1" + vrf: "Vrf_1" + servers: + - address: "client.com" + - address: "host.com" + server_type: "sudo_pam" + map: + override_attribute: + - from: "attr1" + to: "attr2" + map_remote_groups_to_sonic_roles: + - remote_group: "group1" + sonic_roles: + - admin + - operator + idle_timelimit: 20 + - name: "pam" + ssl: "off" + scope: "base" + state: overridden + +# After State: +# ------------ +# +#sonic# show running-configuration | grep ldap +#ldap-server idle-timelimit 20 +#ldap-server pam ssl off +#ldap-server pam scope base +#ldap-server source-interface Eth1/1 +#ldap-server vrf Vrf_1 +#ldap-server host client.com +#ldap-server host host.com use-type sudo_pam +#ldap-server map override-attribute-value attr1 to attr2 +#ldap-server map remote-groups-override-to-sonic-roles group1 to admin,operator +#sonic# + + +""" +RETURN = """ +before: + description: The configuration prior to the module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after: + description: The resulting configuration module invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after(generated): + description: The generated configuration module invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ldap.ldap import LdapArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ldap.ldap import Ldap + + +def main(): + """ + Main entry point for module execution + + :returns: the result from module invocation + """ + module = AnsibleModule(argument_spec=LdapArgs.argument_spec, + supports_check_mode=True) + + result = Ldap(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/sonic_mgmt_servers.py b/plugins/modules/sonic_mgmt_servers.py new file mode 100644 index 000000000..aaa729cca --- /dev/null +++ b/plugins/modules/sonic_mgmt_servers.py @@ -0,0 +1,496 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_mgmt_servers +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: sonic_mgmt_servers +version_added: 2.5.0 +notes: +- Tested against Enterprise SONiC Distribution by Dell Technologies. +- Supports C(check_mode). +short_description: Manage management servers configuration on SONiC +description: + - This module provides configuration management of management servers for devices running SONiC +author: Shade Talabi (@stalabi1) +options: + config: + description: + - Management servers configuration + type: dict + suboptions: + rest: + description: + REST server configuration + type: dict + suboptions: + api_timeout: + description: + - Maximum time in seconds the REST server will wait for a REST API request-response cycle to complete + - Range 0-4294967295 + type: int + default: 900 + client_auth: + description: + - Client authentication methods list + - Specify as a comma separated list. Options for list are password, jwt, cert, and none. + type: str + default: password,jwt + log_level: + description: + - Log level of REST server, range 0-255 + type: int + default: 0 + port: + description: + - Port that the REST server listens on, range 0-65535 + type: int + default: 443 + read_timeout: + description: + - Maximum time in seconds the REST server will wait for an HTTP request-response cycle to complete + - Range 0-4294967295 + type: int + default: 15 + req_limit: + description: + - Maximum number of concurrent requests that the client can make to the REST server + - Range 0-4294967295 + type: int + security_profile: + description: + - Name of security profile + type: str + shutdown: + description: + - Enables/disables REST server from listening on the port + type: bool + vrf: + description: + - Name of VRF + type: str + choices: + - mgmt + telemetry: + description: + - Telemetry server configuration + type: dict + suboptions: + api_timeout: + description: + - Maximum time in seconds the telemetry server will wait for a gNMI request-response cycle to complete + - Range 0-4294967295 + type: int + default: 0 + client_auth: + description: + - Client authentication methods list + - Specify as a comma separated list. Options for list are password, jwt, cert, and none. + type: str + default: password,jwt + jwt_refresh: + description: + - Duration of time in seconds before JWT expires and can be refreshed + - Range 0-4294967295 + type: int + default: 900 + jwt_valid: + description: + - Duration of time in seconds for which JWT is valid on the telemetry server + - Range 0-4294967295 + type: int + default: 3600 + log_level: + description: + - Log level of telemetry server, range 0-255 + type: int + default: 0 + port: + description: + - Port that the telemetry server listens on, range 0-65535 + type: int + default: 8080 + security_profile: + description: + - Name of security profile + type: str + vrf: + description: + - Name of VRF + type: str + choices: + - mgmt + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - deleted + - overridden + - replaced + default: merged +""" +EXAMPLES = """ +# Using Merged +# +# Before state: +# ------------- +# +# sonic# show ip rest +# +# Log level is 0 +# Port is 443 +# Request limit is not-set +# Read timeout is 15 seconds +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 900 seconds +# vrf is not-set +# +# sonic# show ip telemetry +# +# Log level is 0 +# JWT valid is 3600 seconds +# JWT refresh is 900 seconds +# Port is 8080 +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 0 seconds +# vrf is not-set + +- name: Merge mgmt servers configuration + dellemc.enterprise_sonic.sonic_mgmt_servers: + config: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: True + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: cert,jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + state: merged + +# After state: +# ------------ +# +# sonic# show ip rest +# +# Log level is 6 +# Port is 443, disabled +# Request limit is 100 +# Read timeout is 60 seconds +# Client authentication mode is password +# Security profile is profile1 +# API timeout is 120 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 10 +# JWT valid is 300 seconds +# JWT refresh is 80 seconds +# Port is 1234 +# Client authentication mode is cert,jwt +# Security profile is profile2 +# API timeout is 45 seconds +# vrf is mgmt + + +# Using Replaced +# +# Before state: +# ------------- +# +# sonic# show ip rest +# +# Log level is 6 +# Port is 443, disabled +# Request limit is 100 +# Read timeout is 60 seconds +# Client authentication mode is password +# Security profile is profile1 +# API timeout is 120 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 10 +# JWT valid is 300 seconds +# JWT refresh is 80 seconds +# Port is 1234 +# Client authentication mode is cert,jwt +# Security profile is profile2 +# API timeout is 45 seconds +# vrf is mgmt + +- name: Replace mgmt servers configuration + dellemc.enterprise_sonic.sonic_mgmt_servers: + config: + rest: + api_timeout: 180 + vrf: mgmt + telemetry: + log_level: 25 + security_profile: profile2 + state: replaced + +# After state: +# ------------ +# +# sonic# show ip rest +# +# Log level is 0 +# Port is 443 +# Request limit is not-set +# Read timeout is 15 seconds +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 180 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 25 +# JWT valid is 3600 seconds +# JWT refresh is 900 seconds +# Port is 8080 +# Client authentication mode is password,jwt +# Security profile is profile2 +# API timeout is 0 seconds +# vrf is not-set + + +# Using Overridden +# +# Before state: +# ------------- +# +# sonic# show ip rest +# +# Log level is 6 +# Port is 443, disabled +# Request limit is 100 +# Read timeout is 60 seconds +# Client authentication mode is password +# Security profile is profile1 +# API timeout is 120 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 10 +# JWT valid is 300 seconds +# JWT refresh is 80 seconds +# Port is 1234 +# Client authentication mode is cert,jwt +# Security profile is profile2 +# API timeout is 45 seconds +# vrf is mgmt + +- name: Override mgmt servers configuration + dellemc.enterprise_sonic.sonic_mgmt_servers: + config: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: True + vrf: mgmt + state: overridden + +# After state: +# ------------ +# +# sonic# show ip rest +# +# Log level is 6 +# Port is 443, disabled +# Request limit is 100 +# Read timeout is 60 seconds +# Client authentication mode is password +# Security profile is profile1 +# API timeout is 120 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 0 +# JWT valid is 3600 seconds +# JWT refresh is 900 seconds +# Port is 8080 +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 0 seconds +# vrf is not-set + + +# Using Deleted +# +# Before state: +# ------------- +# +# sonic# show ip rest +# +# Log level is 6 +# Port is 443, disabled +# Request limit is 100 +# Read timeout is 60 seconds +# Client authentication mode is password +# Security profile is profile1 +# API timeout is 120 seconds +# vrf is mgmt +# +# sonic# show ip telemetry +# +# Log level is 10 +# JWT valid is 300 seconds +# JWT refresh is 80 seconds +# Port is 1234 +# Client authentication mode is cert,jwt +# Security profile is profile2 +# API timeout is 45 seconds +# vrf is mgmt + +- name: Delete mgmt servers configuration + dellemc.enterprise_sonic.sonic_mgmt_servers: + config: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: True + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: cert,jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + state: deleted + +# After state: +# ------------ +# +# sonic# show ip rest +# +# Log level is 0 +# Port is 443 +# Request limit is not-set +# Read timeout is 15 seconds +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 900 seconds +# vrf is not-set +# +# sonic# show ip telemetry +# +# Log level is 0 +# JWT valid is 3600 seconds +# JWT refresh is 900 seconds +# Port is 8080 +# Client authentication mode is password,jwt +# Security profile is not-set +# API timeout is 0 seconds +# vrf is not-set +""" +RETURN = """ +before: + description: The configuration prior to the module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after: + description: The resulting configuration module invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after(generated): + description: The generated configuration from module invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mgmt_servers.mgmt_servers import Mgmt_serversArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.mgmt_servers.mgmt_servers import Mgmt_servers + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Mgmt_serversArgs.argument_spec, + supports_check_mode=True) + + result = Mgmt_servers(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/sonic_ospf_area.py b/plugins/modules/sonic_ospf_area.py new file mode 100644 index 000000000..c8ab73590 --- /dev/null +++ b/plugins/modules/sonic_ospf_area.py @@ -0,0 +1,1144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_ospf_area""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: sonic_ospf_area +description: This module provides configuration for the area settings of OSPF running on SONiC switches +version_added: "2.5.0" +short_description: configure OSPF area settings on SONiC +author: "Xiao Han (@Xiao_Han2)" +options: + config: + description: + - Specifies configuration for OSPFv2 areas + type: list + elements: dict + suboptions: + area_id: + type: str + required: True + description: + - Area ID of the network (A.B.C.D or 0 to 4294967295). + vrf_name: + type: str + default: 'default' + description: name of the vrf this area belongs to + authentication_type: + type: str + choices: + - message_digest + - text + description: authentication type for area + default_cost: + description: + - Configure NSSA or stub area summary default cost + - range is 0 to 16777215 inclusive + type: int + filter_list_in: + type: str + description: + - inter area prefix filter list. + - Filter incoming prefixes into the area. + - expects name of a prefix list. + filter_list_out: + type: str + description: + - inter area prefix filter list. + - Filter outgoing prefixes from the area. + - expects name of a prefix list. + networks: + type: list + elements: str + description: + - Configure networks in an area + - is a masked ip address + ranges: + type: list + elements: dict + description: Configure address range summarization on border routers + suboptions: + prefix: + type: str + required: True + description: + - address range prefix + - is a masked ip address + advertise: + type: bool + description: enable address range advertising + cost: + type: int + description: + - configure cost of address range + - range is 0 to 16777215 inclusive + substitute: + type: str + description: + - Configure substitute prefix for the address range + - is a masked ip address + shortcut: + type: str + choices: + - default + - disable + - enable + description: Configure area's shortcut mode + stub: + type: dict + description: configuration for stub type area + suboptions: + enabled: + type: bool + description: configure area as stub type area + no_summary: + type: bool + description: disable inter-area route injection into the stub + virtual_links: + type: list + elements: dict + description: configuration for virtual links + suboptions: + enabled: + type: bool + description: enable virtual link + router_id: + type: str + required: True + description: + - router id of the remote ABR + - ip address format + dead_interval: + type: int + description: + - configure adjacency dead interval + - value is in seconds + - range is 1 to 65535 inclusive + hello_interval: + type: int + description: + - configure neighbor hello interval + - value is in seconds + - range is 1 to 65535 inclusive + retransmit_interval: + type: int + description: + - configure LSA retransmit interval + - value is in seconds + - range is 1 to 65535 inclusive + transmit_delay: + type: int + description: + - configure LSA transmit delay + - value is in seconds + - range is 1 to 65535 inclusive + authentication: + type: dict + description: configure authentication settings for virtual link + suboptions: + auth_type: + type: str + choices: + - message_digest + - text + - none + description: authentication type for virtual link + key: + type: str + description: text authentication password for virtual link + key_encrypted: + type: bool + description: password is in encrypted format + message_digest_list: + type: list + elements: dict + description: + - configure message-digest authentication keys + - For deletion, only the key_id is used. + suboptions: + key_id: + type: int + required: True + description: + - message-digest authentication key id + - range is 1 to 255 inclusive + key: + type: str + description: authentication password (ignored for deletion) + key_encrypted: + type: bool + description: password is in encrypted format (ignored for deletion) + state: + description: + - Specifies the type of configuration update to be performed on the device. + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" +EXAMPLES = """ +# NOTE: Configuration of an OSPF network instance (VRF) is required before an OSPF "area" can +# be configured in association with that network instance (VRF). + +# using merged to add or change ospf_area settings + # merging all settings for an area + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: merge examples of all settings + sonic_ospf_area: + state: merged + config: + - area_id: 2 + vrf_name: Vrf1 + authentication_type: message_digest + default_cost: 3 + stub: + enabled: True + no_summary: True + shortcut: default + - area_id: 3 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + ranges: + - prefix: 1.1.1.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: False + substitute: 2.2.2.2/24 + - prefix: 1.1.1.4/24 + advertise: True + cost: 10 + substitute: 3.3.3.3/24 + - area_id: 4 + vrf_name: Vrf1 + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 5 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + hello_interval: 30 + retransmit_interval: 40 + transmit_delay: 50 + authentication: + auth_type: text + key: "U2FsdGVkX197YJtZ/3Ac6n5kRIG/ZqeU1/wC0cVFyfU=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX1/wbqjMB7Lr+Mm3wY8+lCdaqUmG2rr9Adw=" + key_encrypted: True + - key_id: 2 + key: "U2FsdGVkX18Czj9r8skDrg/wtpwTKKCQ8FXUehpCmHc=" + key_encrypted: True + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.2 authentication message-digest + # area 0.0.0.2 stub no-summary + # area 0.0.0.2 default-cost 3 + # area 0.0.0.2 shortcut default + # area 0.0.0.3 filter-list prefix pf1 in + # area 0.0.0.3 filter-list prefix pf2 out + # area 0.0.0.4 + # area 0.0.0.5 + # area 0.0.0.5 virtual-link 34.7.35.1 + # area 0.0.0.5 virtual-link 34.7.35.2 + # area 0.0.0.5 virtual-link 34.7.35.2 authentication + # area 0.0.0.5 virtual-link 34.7.35.2 authentication-key U2FsdGVkX197YJtZ/3Ac6n5kRIG/ZqeU1/wC0cVFyfU= encrypted + # area 0.0.0.5 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.5 virtual-link 34.7.35.2 hello-interval 30 + # area 0.0.0.5 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.5 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.5 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX1/wbqjMB7Lr+Mm3wY8+lCdaqUmG2rr9Adw= encrypted + # area 0.0.0.5 virtual-link 34.7.35.2 message-digest-key 2 md5 U2FsdGVkX18Czj9r8skDrg/wtpwTKKCQ8FXUehpCmHc= encrypted + # area 0.0.0.3 range 1.1.1.1/24 + # area 0.0.0.3 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.3 range 1.1.1.3/24 not-advertise + # area 0.0.0.3 range 1.1.1.4/24 advertise cost 10 + # area 0.0.0.3 range 1.1.1.4/24 substitute 3.3.3.3/24 + # network 1.1.1.1/24 area 0.0.0.4 + # network 23.235.75.1/23 area 0.0.0.4 + # network 3.5.1.5/23 area 0.0.0.4 + # ! + # router ospf vrf Vrf2 + # ! + # ----- + + # minimum data for config subsections + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: merge smallest group of settings + sonic_ospf_area: + state: merged + config: + - area_id: 0.0.0.2 + vrf_name: Vrf1 + networks: + - 1.1.1.1/24 + - area_id: 0.0.0.3 + vrf_name: Vrf1 + ranges: + - prefix: 1.1.1.1/24 + - area_id: 0.0.0.4 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + - area_id: 0.0.0.5 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + message_digest_list: + - key_id: 1 + key: grighr + # NOTE: The existence of an 'area' is only displayed by this Ansible module if configuration options are + # currently configured for that area. (An "area" that currently has no configured sub-options is not displayed.) + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.2 + # area 0.0.0.3 + # area 0.0.0.4 + # area 0.0.0.5 + # area 0.0.0.4 virtual-link 34.7.35.1 + # area 0.0.0.5 virtual-link 34.7.35.1 + # area 0.0.0.5 virtual-link 34.7.35.1 message-digest-key 1 md5 U2FsdGVkX19oCaX2HsxLR2nWtyK15AfE7ajHVjzgoaY= encrypted + # area 0.0.0.3 range 1.1.1.1/24 + # network 1.1.1.1/24 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + # ----- + + # merging and making changes to attributes + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 authentication message-digest + # area 0.0.0.1 stub no-summary + # area 0.0.0.1 default-cost 6 + # area 0.0.0.1 filter-list prefix pf1 in + # area 0.0.0.1 filter-list prefix pf2 out + # area 0.0.0.1 shortcut disable + # area 0.0.0.1 virtual-link 1.1.1.1 + # area 0.0.0.1 virtual-link 1.1.1.1 authentication + # area 0.0.0.1 virtual-link 1.1.1.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 dead-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 hello-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 retransmit-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 transmit-delay 10 + # area 0.0.0.1 virtual-link 1.1.1.2 + # area 0.0.0.1 virtual-link 1.1.1.2 dead-interval 34 + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 1 md5 U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 2 md5 U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY= encrypted + # area 0.0.0.1 range 1.1.1.1/24 not-advertise + # area 0.0.0.1 range 1.1.1.2/24 advertise + # network 1.1.1.1/24 area 0.0.0.1 + # network 1.1.1.2/24 area 0.0.0.1 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "test merge all settings" + sonic_ospf_area: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: text + default_cost: 5 + filter_list_in: pf2 + filter_list_out: pf1 + networks: + - 1.1.1.5/24 + ranges: + - prefix: 1.1.1.1/24 + advertise: True + cost: 12 + substitute: 11.11.1.1/24 + - prefix: 1.1.1.2/24 + advertise: False + shortcut: enable + stub: + enabled: True + no_summary: False + virtual_links: + - router_id: 1.1.1.1 + enabled: True + dead_interval: 45 + hello_interval: 21 + retransmit_interval: 15 + transmit_delay: 23 + authentication: + auth_type: text + key: "U2FsdGVkX1/lz7KE/onDUAhQU2nftsm/nddLb2ZvYSQ=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "somepass" + - router_id: 1.1.1.2 + dead_interval: 16 + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 authentication + # area 0.0.0.1 stub + # area 0.0.0.1 default-cost 5 + # area 0.0.0.1 filter-list prefix pf2 in + # area 0.0.0.1 filter-list prefix pf1 out + # area 0.0.0.1 shortcut enable + # area 0.0.0.1 virtual-link 1.1.1.1 + # area 0.0.0.1 virtual-link 1.1.1.1 authentication + # area 0.0.0.1 virtual-link 1.1.1.1 authentication-key U2FsdGVkX1/lz7KE/onDUAhQU2nftsm/nddLb2ZvYSQ= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 dead-interval 45 + # area 0.0.0.1 virtual-link 1.1.1.1 hello-interval 21 + # area 0.0.0.1 virtual-link 1.1.1.1 retransmit-interval 15 + # area 0.0.0.1 virtual-link 1.1.1.1 transmit-delay 23 + # area 0.0.0.1 virtual-link 1.1.1.2 + # area 0.0.0.1 virtual-link 1.1.1.2 dead-interval 16 + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 1 md5 U2FsdGVkX18D0swlrl3pVzMGxRZYzY58X06jPq2CrNU= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 2 md5 U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY= encrypted + # area 0.0.0.1 range 1.1.1.1/24 advertise cost 12 + # area 0.0.0.1 range 1.1.1.1/24 substitute 11.11.1.1/24 + # area 0.0.0.1 range 1.1.1.2/24 not-advertise + # network 1.1.1.1/24 area 0.0.0.1 + # network 1.1.1.2/24 area 0.0.0.1 + # network 1.1.1.5/24 area 0.0.0.1 + # ! + # router ospf vrf Vrf2 + # ! + # ----- + + # merging different keys + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "test merge different keys" + sonic_ospf_area: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + virtual_links: + - router_id: 1.1.1.1 + authentication: + key: qwerty + key_encrypted: False + - router_id: 1.1.1.3 + authentication: + key: "U2FsdGVkX1/lz7KE/onDUAhQU2nftsm/nddLb2ZvYSQ=" + key_encrypted: True + - router_id: 1.1.1.4 + authentication: + key: somepass + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 + # area 0.0.0.1 virtual-link 1.1.1.1 + # area 0.0.0.1 virtual-link 1.1.1.1 authentication-key U2FsdGVkX180JKbs3Rf5IyLot8UW0/srcXdGaQXEHiw= encrypted + # area 0.0.0.1 virtual-link 1.1.1.3 + # area 0.0.0.1 virtual-link 1.1.1.3 authentication-key U2FsdGVkX1/lz7KE/onDUAhQU2nftsm/nddLb2ZvYSQ= encrypted + # area 0.0.0.1 virtual-link 1.1.1.4 + # area 0.0.0.1 virtual-link 1.1.1.4 authentication-key U2FsdGVkX1+2i/anKXKpEfwZIAkb1Hzkx1nH2IBnlMA= encrypted + # ! + # router ospf vrf Vrf2 + # ! + # Note: the device automatically converts keys to encrypted format +# ---------- + + +# using deleted to remove ospf settings + # deleting all settings for areas + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 authentication message-digest + # area 0.0.0.1 stub no-summary + # area 0.0.0.1 default-cost 6 + # area 0.0.0.1 filter-list prefix pf1 in + # area 0.0.0.1 filter-list prefix pf2 out + # area 0.0.0.1 shortcut disable + # area 0.0.0.2 stub no-summary + # area 0.0.0.2 shortcut disable + # area 0.0.0.3 shortcut default + # area 0.0.0.1 virtual-link 1.1.1.1 + # area 0.0.0.1 virtual-link 1.1.1.1 authentication + # area 0.0.0.1 virtual-link 1.1.1.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 dead-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 hello-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 retransmit-interval 10 + # area 0.0.0.1 virtual-link 1.1.1.1 transmit-delay 10 + # area 0.0.0.1 virtual-link 1.1.1.2 + # area 0.0.0.1 virtual-link 1.1.1.2 dead-interval 34 + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 1 md5 U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM= encrypted + # area 0.0.0.1 virtual-link 1.1.1.1 message-digest-key 2 md5 U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY= encrypted + # area 0.0.0.1 range 1.1.1.1/24 not-advertise + # area 0.0.0.1 range 1.1.1.2/24 advertise + # area 0.0.0.2 range 1.1.1.1/24 advertise + # area 0.0.0.3 range 1.1.4.6/24 cost 14 + # network 1.1.1.1/24 area 0.0.0.1 + # network 1.1.1.2/24 area 0.0.0.1 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "test delete all settings for areas" + sonic_ospf_area: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + - area_id: 0.0.0.2 + vrf_name: Vrf1 + ranges: + - prefix: 1.1.1.1/24 + advertise: True + shortcut: disable + stub: + enabled: True + no_summary: True + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.3 shortcut default + # area 0.0.0.3 range 1.1.4.6/24 cost 14 + # ! + # router ospf vrf Vrf2 + # ! + # ----- + + + # clearing subsections of config + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 shortcut default + # area 0.0.0.2 authentication message-digest + # area 0.0.0.3 filter-list prefix pf1 in + # area 0.0.0.4 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 + # area 0.0.0.3 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.3 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.4 virtual-link 34.7.35.1 + # area 0.0.0.4 virtual-link 34.7.35.1 authentication + # area 0.0.0.4 virtual-link 34.7.35.1 authentication-key U2FsdGVkX1/lz7KE/onDUAhQU2nftsm/nddLb2ZvYSQ= encrypted + # area 0.0.0.4 virtual-link 34.7.35.1 dead-interval 10 + # area 0.0.0.4 virtual-link 34.7.35.2 + # area 0.0.0.4 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.1 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.1 range 1.1.1.3/24 not-advertise + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "test clear subsections" + sonic_ospf_area: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + ranges: [] + - area_id: 0.0.0.2 + vrf_name: Vrf1 + networks: [] + - area_id: 0.0.0.3 + vrf_name: Vrf1 + virtual_links: [] + - area_id: 4 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + authentication: {} + - router_id: 34.7.35.2 + message_digest_list: [] + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 shortcut default + # area 0.0.0.2 authentication message-digest + # area 0.0.0.3 filter-list prefix pf1 in + # area 0.0.0.4 + # area 0.0.0.4 virtual-link 34.7.35.1 + # area 0.0.0.4 virtual-link 34.7.35.1 dead-interval 10 + # area 0.0.0.4 virtual-link 34.7.35.2 + # area 0.0.0.4 virtual-link 34.7.35.2 dead-interval 10 + # ! + # router ospf vrf Vrf2 + # ! + # ----- + + # deleting individual attributes + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 filter-list prefix pf1 in + # area 0.0.0.1 filter-list prefix pf2 out + # area 0.0.0.2 authentication message-digest + # area 0.0.0.3 + # area 0.0.0.4 stub no-summary + # area 0.0.0.4 default-cost 3 + # area 0.0.0.4 shortcut default + # area 0.0.0.5 stub + # area 0.0.0.5 default-cost 5 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 + # area 0.0.0.3 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.3 virtual-link 34.7.35.2 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.3 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.3 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.1 range 1.1.1.1/24 advertise cost 13 + # area 0.0.0.1 range 1.1.1.1/24 substitute 11.2.5.1/24 + # area 0.0.0.1 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.1 range 1.1.1.3/24 advertise + # area 0.0.0.1 range 1.1.1.3/24 substitute 2.2.2.2/24 + # area 0.0.0.1 range 1.1.1.4/24 advertise cost 34 + # area 0.0.0.1 range 1.1.1.4/24 substitute 3.3.3.3/24 + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "test clear subsections" + sonic_ospf_area: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + ranges: + - prefix: 1.1.1.1/24 + - prefix: 1.1.1.2/24 + cost: 4 + - prefix: 1.1.1.3/24 + substitute: 2.2.2.2/24 + - prefix: 1.1.1.4/24 + advertise: True + - area_id: 0.0.0.2 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + authentication: + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + stub: + enabled: True + no_summary: True + - area_id: 5 + vrf_name: Vrf1 + default_cost: 5 + stub: + enabled: True + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 + # area 0.0.0.2 + # area 0.0.0.3 + # area 0.0.0.4 default-cost 3 + # area 0.0.0.5 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 + # area 0.0.0.3 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.3 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.1 range 1.1.1.2/24 advertise + # area 0.0.0.1 range 1.1.1.3/24 advertise + # area 0.0.0.1 range 1.1.1.4/24 cost 34 + # area 0.0.0.1 range 1.1.1.4/24 substitute 3.3.3.3/24 + # network 23.235.75.1/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + # ----- +# ---------- + + +# using replaced + # replace listed areas + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 filter-list prefix pf1 in + # area 0.0.0.1 filter-list prefix pf2 out + # area 0.0.0.2 authentication message-digest + # area 0.0.0.3 + # area 0.0.0.4 stub no-summary + # area 0.0.0.4 default-cost 3 + # area 0.0.0.4 shortcut default + # area 0.0.0.5 stub + # area 0.0.0.5 default-cost 5 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 + # area 0.0.0.3 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.3 virtual-link 34.7.35.2 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.3 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.3 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.1 range 1.1.1.1/24 advertise cost 13 + # area 0.0.0.1 range 1.1.1.1/24 substitute 11.2.5.1/24 + # area 0.0.0.1 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.1 range 1.1.1.3/24 advertise + # area 0.0.0.1 range 1.1.1.3/24 substitute 2.2.2.2/24 + # area 0.0.0.1 range 1.1.1.4/24 advertise cost 34 + # area 0.0.0.1 range 1.1.1.4/24 substitute 3.3.3.3/24 + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "replace areas" + sonic_ospf_area: + state: replaced + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + default_cost: 5 + stub: + enabled: True + no_summary: False + - area_id: 0.0.0.2 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + shortcut: default + default_cost: 3 + stub: + enabled: True + no_summary: True + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + ranges: + - prefix: 1.1.1.1/24 + advertise: True + substitute: 11.2.5.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: True + substitute: 2.5.3.78/24 + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + - router_id: 34.7.35.2 + transmit_delay: 50 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX18mUZjlJL/Q/7vYtx2AUyDc+NcLKc/BOJUA=" + key_encrypted: True + - key_id: 3 + key: "U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo=" + key_encrypted: True + authentication: + auth_type: message_digest + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 authentication message-digest + # area 0.0.0.1 stub + # area 0.0.0.1 default-cost 5 + # area 0.0.0.2 authentication message-digest + # area 0.0.0.2 stub no-summary + # area 0.0.0.2 default-cost 3 + # area 0.0.0.2 filter-list prefix pf1 in + # area 0.0.0.2 filter-list prefix pf2 out + # area 0.0.0.2 shortcut default + # area 0.0.0.3 + # area 0.0.0.4 shortcut default + # area 0.0.0.5 stub + # area 0.0.0.5 default-cost 5 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.1 + # area 0.0.0.4 virtual-link 34.7.35.1 authentication + # area 0.0.0.4 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.4 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.4 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.2 + # area 0.0.0.4 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.4 virtual-link 34.7.35.2 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.4 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.4 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.4 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.3 range 1.1.1.1/24 advertise + # area 0.0.0.3 range 1.1.1.1/24 substitute 11.2.5.1/24 + # area 0.0.0.3 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.3 range 1.1.1.3/24 advertise + # area 0.0.0.3 range 1.1.1.3/24 substitute 2.5.3.78/24 + # network 1.1.1.1/24 area 0.0.0.1 + # network 23.235.75.1/23 area 0.0.0.1 + # network 3.5.1.5/23 area 0.0.0.1 + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! +# ---------- + + +# using overridden + # override listed areas + # Before state: + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 filter-list prefix pf1 in + # area 0.0.0.1 filter-list prefix pf2 out + # area 0.0.0.2 authentication message-digest + # area 0.0.0.3 + # area 0.0.0.4 stub no-summary + # area 0.0.0.4 default-cost 3 + # area 0.0.0.4 shortcut default + # area 0.0.0.5 stub + # area 0.0.0.5 default-cost 5 + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 + # area 0.0.0.3 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.3 virtual-link 34.7.35.2 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.3 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.3 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.3 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.1 range 1.1.1.1/24 advertise cost 13 + # area 0.0.0.1 range 1.1.1.1/24 substitute 11.2.5.1/24 + # area 0.0.0.1 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.1 range 1.1.1.3/24 advertise + # area 0.0.0.1 range 1.1.1.3/24 substitute 2.2.2.2/24 + # area 0.0.0.1 range 1.1.1.4/24 advertise cost 34 + # area 0.0.0.1 range 1.1.1.4/24 substitute 3.3.3.3/24 + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! + + # example: + - name: "override areas" + sonic_ospf_area: + state: overridden + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + default_cost: 5 + stub: + enabled: True + no_summary: False + - area_id: 0.0.0.2 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + shortcut: default + default_cost: 3 + stub: + enabled: True + no_summary: True + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + ranges: + - prefix: 1.1.1.1/24 + advertise: True + substitute: 11.2.5.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: True + substitute: 2.5.3.78/24 + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + - router_id: 34.7.35.2 + transmit_delay: 50 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA=" + key_encrypted: True + - key_id: 3 + key: "U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo=" + key_encrypted: True + authentication: + auth_type: message_digest + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + + # After state + # sonic# show running-configuration ospf + # router ospf vrf Vrf1 + # area 0.0.0.1 authentication message-digest + # area 0.0.0.1 stub + # area 0.0.0.1 default-cost 5 + # area 0.0.0.2 authentication message-digest + # area 0.0.0.2 stub no-summary + # area 0.0.0.2 default-cost 3 + # area 0.0.0.2 filter-list prefix pf1 in + # area 0.0.0.2 filter-list prefix pf2 out + # area 0.0.0.2 shortcut default + # area 0.0.0.3 + # area 0.0.0.4 shortcut default + # area 0.0.0.3 virtual-link 34.7.35.1 + # area 0.0.0.3 virtual-link 34.7.35.1 authentication + # area 0.0.0.3 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.3 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.3 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.1 + # area 0.0.0.4 virtual-link 34.7.35.1 authentication + # area 0.0.0.4 virtual-link 34.7.35.1 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.4 virtual-link 34.7.35.1 hello-interval 30 + # area 0.0.0.4 virtual-link 34.7.35.1 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.2 + # area 0.0.0.4 virtual-link 34.7.35.2 authentication message-digest + # area 0.0.0.4 virtual-link 34.7.35.2 authentication-key U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= encrypted + # area 0.0.0.4 virtual-link 34.7.35.2 dead-interval 10 + # area 0.0.0.4 virtual-link 34.7.35.2 retransmit-interval 40 + # area 0.0.0.4 virtual-link 34.7.35.2 transmit-delay 50 + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 1 md5 U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= encrypted + # area 0.0.0.4 virtual-link 34.7.35.2 message-digest-key 3 md5 U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= encrypted + # area 0.0.0.3 range 1.1.1.1/24 advertise + # area 0.0.0.3 range 1.1.1.1/24 substitute 11.2.5.1/24 + # area 0.0.0.3 range 1.1.1.2/24 advertise cost 4 + # area 0.0.0.3 range 1.1.1.3/24 advertise + # area 0.0.0.3 range 1.1.1.3/24 substitute 2.5.3.78/24 + # network 1.1.1.1/24 area 0.0.0.1 + # network 23.235.75.1/23 area 0.0.0.1 + # network 3.5.1.5/23 area 0.0.0.1 + # network 1.1.1.1/24 area 0.0.0.2 + # network 23.235.75.1/23 area 0.0.0.2 + # network 3.5.1.5/23 area 0.0.0.2 + # ! + # router ospf vrf Vrf2 + # ! +# ---------- +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: [{"config": ..., "state": ...}, {"config": ..., "state": ...}] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospf_area.ospf_area import Ospf_areaArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospf_area.ospf_area import Ospf_area + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Ospf_areaArgs.argument_spec, + supports_check_mode=True) + + result = Ospf_area(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/sonic_ospfv2.py b/plugins/modules/sonic_ospfv2.py new file mode 100644 index 000000000..cd29f42b8 --- /dev/null +++ b/plugins/modules/sonic_ospfv2.py @@ -0,0 +1,829 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_ospfv2 +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: sonic_ospfv2 +version_added: '2.5.0' +notes: +- Supports C(check_mode). +short_description: Configure global OSPFv2 protocol settings on SONiC. +description: + - This module provides configuration management of global OSPFv2 parameters on devices running SONiC. + - Configure VRF instance before configuring OSPF in a VRF. +author: "Santhosh kumar T (@santhosh-kt)" +options: + config: + description: + - Specifies the OSPFv2 related configuration. + - I(non_passive_interfaces) and I(passive_interfaces) are mutually exclusive. + - When I(default_passive=True), I(passive_interfaces) cannot be configured. + - When I(default_passive=False), I(non_passive_interfaces) cannot be configured. + type: list + elements: dict + suboptions: + abr_type: + description: + - Configure router ABR type. + - C(cisco) - Cisco implementation type ABR. + - C(ibm) - IBM implementation type ABR. + - C(shortcut) - Shortcut ABR. + - C(standard) - RFC2328 Standard implementation ABR. + type: str + choices: + - cisco + - ibm + - shortcut + - standard + auto_cost_reference_bandwidth: + description: + - Configure interface auto cost reference bandwidth (1 to 4294967). + type: int + default_metric: + description: + - Configure metric for redistributed routes (0 to 16777214). + type: int + default_passive: + description: + - Suppresses OSPFv2 routing updates on all interfaces. + type: bool + distance: + description: + - Configure route administrative distance. + type: dict + suboptions: + all: + description: + - Distance value for all type of routes (1 to 255). + type: int + external: + description: + - Distance value for external routes (1 to 255). + type: int + inter_area: + description: + - Distance value for inter-area routes (1 to 255). + type: int + intra_area: + description: + - Distance value for intra-area routes (1 to 255). + type: int + graceful_restart: + description: + - OSPF non stop forwarding (NSF) also known as OSPF Graceful Restart. + type: dict + suboptions: + enable: + description: + - Enable graceful restart. + type: bool + grace_period: + description: + - Maximum length of the grace period in seconds (1 to 1800). + type: int + helper: + description: + - OSPF GR Helper. + type: dict + suboptions: + enable: + description: + - Enable Helper support. + type: bool + advertise_router_id: + description: + - Advertising Router ID. + type: list + elements: str + planned_only: + description: + - Supported only planned restart. + type: bool + strict_lsa_checking: + description: + - Enable strict LSA check. + type: bool + supported_grace_time: + description: + - Supported grace interval in seconds (10 to 1800). + type: int + log_adjacency_changes: + description: + - Enable OSPFv2 adjacency state logs. + type: str + choices: + - brief + - detail + max_metric: + description: + - Enables infinite metric advertising in OSPFv2 LSAs. + type: dict + suboptions: + administrative: + description: + - Enables administrative type infinite metric advertising. + type: bool + external_lsa_all: + description: + - Configure external LSA all prefix max metric advertising. + - Configure the maximum metric value (1 to 16777215). + type: int + external_lsa_connected: + description: + - Configure external LSA connected prefix max metric advertising. + - Configure the maximum metric value (1 to 16777215). + type: int + on_startup: + description: + - Enables infinite metric advertising at OSPFv2 router startup (5 to 86400). + type: int + router_lsa_all: + description: + - Configure router LSA all link max metric advertising. + - Configure the maximum metric value (1 to 16777215). + type: int + router_lsa_stub: + description: + - Configure router LSA stub link max metric advertising. + - Configure the maximum metric value (1 to 16777215). + type: int + maximum_paths: + description: + - Configure maximum number of multiple paths for ECMP support (1 to 256). + type: int + non_passive_interfaces: + description: + - Configure non passive interface types. + type: list + elements: dict + suboptions: + addresses: + description: + - Configure Interface IPv4 addresses. + type: list + elements: str + interface: + description: + - Full name of the Layer 3 interface, i.e. Eth1/1. + type: str + required: true + opaque_lsa_capability: + description: + - Enables opaque LSA capability. + type: bool + passive_interfaces: + description: + - Configure passive interface types. + type: list + elements: dict + suboptions: + addresses: + description: + - Configure Interface IPv4 addresses. + type: list + elements: str + interface: + description: + - Full name of the Layer 3 interface, i.e. Eth1/1. + type: str + required: true + redistribute: + description: + - Configure route redistribution into OSPFv2 router. + type: list + elements: dict + suboptions: + always: + description: + - Enable default route redistribution into OSPF always. + - Only available for I(protocol=default_route). + type: bool + metric: + description: + - Metric value for redistributed routes (0 to 16777214). + type: int + metric_type: + description: + - Metric type for redistributed routes. + type: int + choices: + - 1 + - 2 + protocol: + description: + - Configure the type of protocol to redistribute into OSPF. + - Deleting I(protocol) alone will also delete all the other configuration under I(redistribute). + - C(bgp) - Border Gateway Protocol. + - C(connected) - Directly connected or attached subnets and hosts. + - C(default_route) - Default routes. + - C(kernel) - Kernel routes other than FRR installed routes. + - C(static) - Statically configured routes. + type: str + choices: + - bgp + - connected + - default_route + - kernel + - static + required: true + route_map: + description: + - Route map to filter redistributed routes. + - Configure route map before. + type: str + refresh_timer: + description: + - Configures LSA refresh interval in seconds (10 to 1800). + type: int + rfc1583_compatible: + description: + - Enable OSPFv2 RFC compatibility. + type: bool + router_id: + description: + - Configure OSPFv2 router identifier (A.B.C.D). + type: str + timers: + description: + - Configures router timers. + type: dict + suboptions: + lsa_min_arrival: + description: + - LSA minimum arrival timer in milliseconds (0 to 600000). + type: int + throttle_lsa_all: + description: + - LSA delay between transmissions in milliseconds (0 to 5000). + type: int + throttle_spf: + description: + - OSPFv2 SPF timers. + - I(delay_time), I(initial_hold_time) and I(maximum_hold_time) are required together. + type: dict + suboptions: + delay_time: + description: + - SPF delay time in milliseconds (0 to 600000). + type: int + initial_hold_time: + description: + - SPF initial hold time in milliseconds (0 to 600000). + type: int + maximum_hold_time: + description: + - SPF maximum hold time in milliseconds (0 to 600000). + type: int + write_multiplier: + description: + - Specifies the maximum number of interfaces serviced per write (1 to 100) + type: int + vrf_name: + description: + - Specifies the vrf name. + type: str + default: 'default' + state: + description: + - Specifies the operation to be performed on the OSPFv2 process configured on the device. + - In case of merged, the input configuration will be merged with the existing OSPFv2 configuration on the device. + - In case of deleted, the existing OSPFv2 configuration will be removed from the device. + - In case of overridden, all the existing OSPFv2 configuration will be deleted and the specified input configuration will be installed. + - In case of replaced, the existing OSPFv2 configuration on the device will be replaced by the configuration + in the playbook for each VRF group configured by the playbook. + type: str + default: merged + choices: ['merged', 'deleted', 'replaced', 'overridden'] +""" +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# default-metric 100 +# max-metric router-lsa external-lsa all 2 +# passive-interface default +# timers throttle spf 50 20 10 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# ospf router-id 20.20.20.20 +# distance 30 +# distance ospf external 20 +# refresh timer 300 +# write-multiplier 20 +# maximum-paths 200 +# passive-interface Eth1/2 3.3.3.3 +# passive-interface Eth1/3 +#! +#sonic# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#sonic# + + - name: Delete the OSPFv2 configurations + sonic_ospf: + config: + - vrf_name: 'default' + router_id: "20.20.20.20" + distance: + external: 20 + default_passive: false + maximum_paths: 200 + passive_interfaces: + interfaces: + - interface: 'Eth1/3' + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "RMAP" + refresh_timer: 300 + - vrf_name: "Vrf_1" + timers: + throttle_spf: + delay_time: 50 + initial_hold_time: 20 + maximum_hold_time: 10 + default_metric: 100 + max_metric: + external_lsa_all: 2 + non_passive_interfaces: + interfaces: + - interface: "Eth1/2" + addresses: + - "2.2.2.2" + state: deleted + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# passive-interface default +# timers throttle lsa all 300 +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# distance 30 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +#! +#sonic# + + +# Using deleted + +# Before state: +# ------------- +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# passive-interface default +# timers throttle lsa all 300 +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# distance 30 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +#! +#sonic# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#sonic# + + - name: Delete the OSPFv2 configurations + sonic_ospf: + config: + - vrf_name: "Vrf_1" + state: deleted + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf +# distance 30 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +#! +#sonic# + + +# Using merged + +# Before state: +# ------------- +# +# sonic# show running-configuration ospf +# (No ospf configuration present) +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#sonic# + + - name: Add the OSPFv2 configurations + sonic_ospf: + config: + - vrf_name: 'default' + router_id: "10.10.10.10" + distance: + external: 20 + auto_cost_reference_bandwidth: 100 + - vrf_name: "Vrf_1" + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "RMAP" + default_passive: true + non_passive_interfaces: + interfaces: + - interface: "Eth1/1" + addresses: + - "2.2.2.2" + state: merged + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# passive-interface default +# timers throttle spf 10 20 50 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +#! +#router ospf +# auto-cost reference-bandwidth 100 +# ospf router-id 10.10.10.10 +# distance ospf external 20 +#! +#sonic# + + +# Using merged + +# Before state: +# ------------- +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# passive-interface default +# timers throttle spf 10 20 50 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +#! +#router ospf +# ospf router-id 10.10.10.10 +# distance ospf external 20 +#! +#sonic# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#sonic# + + - name: Add the OSPFv2 configurations + sonic_ospf: + config: + - vrf_name: 'default' + write_multiplier: 20 + router_id: "20.20.20.20" + distance: + all: 30 + default_passive: false + graceful_restart: + enable: true + grace_period: 100 + helper: + enable: true + planned_only: true + advertise_router_id: + - '1.1.1.1' + - '2.2.2.2' + passive_interfaces: + interfaces: + - interface: 'Eth1/2' + addresses: + - '3.3.3.3' + - interface: 'Eth1/3' + log_adjacency_changes: 'detail' + - vrf_name: "Vrf_1" + timers: + throttle_spf: + delay_time: 50 + initial_hold_time: 20 + maximum_hold_time: 10 + max_metric: + external_lsa_all: 30 + log_adjacency_changes: 'brief' + default_passive: true + non_passive_interfaces: + interfaces: + - interface: "Eth1/2" + addresses: + - "2.2.2.2" + state: merged + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# log-adjacency-changes +# max-metric router-lsa external-lsa all 30 +# passive-interface default +# timers throttle spf 50 20 10 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# ospf router-id 20.20.20.20 +# distance 30 +# distance ospf external 20 +# log-adjacency-changes detail +# graceful-restart grace-period 100 +# graceful-restart helper enable +# graceful-restart helper planned-only +# graceful-restart helper enable 1.1.1.1 +# graceful-restart helper enable 2.2.2.2 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +# passive-interface Eth1/3 +#! +#sonic# + + +# Using replaced + +# Before state: +# ------------- +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# max-metric router-lsa external-lsa all 2 +# passive-interface default +# timers throttle spf 50 20 10 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# ospf router-id 20.20.20.20 +# distance 30 +# distance ospf external 20 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +# passive-interface Eth1/3 +#! +#sonic# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#route-map RMAP2 permit 2 +#sonic# + + - name: Replace the OSPFv2 vrf default configurations + sonic_ospf: + config: + - vrf_name: 'default' + router_id: "20.20.20.20" + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "RMAP2" + - protocol: "default_route" + always: true + route_map: "RMAP" + distance: + all: 20 + abr_type: cisco + opaque_lsa_capability: true + state: replaced + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# max-metric router-lsa external-lsa all 2 +# passive-interface default +# timers throttle spf 50 20 10 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# capability opaque +# ospf router-id 20.20.20.20 +# default-information originate always route-map RMAP +# distance 20 +# ospf abr-type cisco +# redistribute connected metric 15 metric-type 2 route-map RMAP2 +#! + + +# Using overridden + +# Before state: +# ------------- +# +#sonic# show running-configuration ospf +#router ospf vrf Vrf_1 +# max-metric router-lsa external-lsa all 2 +# passive-interface default +# timers throttle spf 50 20 10 +# timers throttle lsa all 300 +# redistribute bgp metric 15 metric-type 2 route-map RMAP +# no passive-interface Eth1/1 2.2.2.2 +# no passive-interface Eth1/2 2.2.2.2 +#! +#router ospf +# ospf router-id 20.20.20.20 +# distance 30 +# distance ospf external 20 +# write-multiplier 20 +# passive-interface Eth1/2 3.3.3.3 +# passive-interface Eth1/3 +#! +#sonic# +#sonic# show running-configuration vrf Vrf_1 +#! +#ip vrf Vrf_1 +#sonic# show running-configuration ip prefix-list +#! +#ip prefix-list PRF_LIST seq 1 permit 1.1.1.1/24 +#ip prefix-list PRF_LIST2 seq 1 permit 1.1.1.1/24 +#sonic# show running-configuration route-map +#! +#route-map RMAP permit 1 +#route-map RMAP2 permit 2 +#sonic# + + - name: Override the OSPFv2 configurations + sonic_ospf: + config: + - vrf_name: 'default' + router_id: "20.20.20.20" + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "RMAP2" + distance: + all: 20 + rfc1583_compatible: true + state: overridden + +# After state: +# ------------ +# +#sonic# show running-configuration ospf +#router ospf +# compatible rfc1583 +# ospf router-id 20.20.20.20 +# distance 20 +# redistribute connected metric 15 metric-type 2 route-map RMAP2 +#! + + +""" +RETURN = """ +before: + description: The configuration prior to the module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after: + description: The resulting configuration module invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after(generated): + description: The generated configuration module invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospfv2.ospfv2 import Ospfv2Args +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospfv2.ospfv2 import Ospfv2 + + +def main(): + """ + Main entry point for module execution + + :returns: the result from module invocation + """ + module = AnsibleModule(argument_spec=Ospfv2Args.argument_spec, + supports_check_mode=True) + + result = Ospfv2(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/sonic_ospfv2_interfaces.py b/plugins/modules/sonic_ospfv2_interfaces.py new file mode 100644 index 000000000..6779f1c39 --- /dev/null +++ b/plugins/modules/sonic_ospfv2_interfaces.py @@ -0,0 +1,747 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_ospfv2_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: sonic_ospfv2_interfaces +version_added: '2.5.0' +notes: +- Supports C(check_mode). +short_description: Configure OSPFv2 interface mode protocol settings on SONiC. +description: + - This module provides configuration management of OSPFv2 interface mode parameters on devices running SONiC. + - Configure VRF instance before configuring OSPF in a VRF. + - Configure OSPF instance before configuring OSPF in interfaces. +author: "Santhosh kumar T (@santhosh-kt)" +options: + config: + description: + - Specifies the OSPFv2 interface configurations. + type: list + elements: dict + suboptions: + name: + required: True + type: str + description: + - Full name of the interface, i.e. Ethernet1. + ospf_attributes: + description: + - Specifies OSPFv2 configurations for the interface. + - If I(address) is not specified, the IPv4 address of the interface is considered. + - I(dead_interval) and I(hello_multiplier) are mutually exclusive. + type: list + elements: dict + suboptions: + address: + description: + - Specifies the interface IPv4 address. + type: str + area_id: + description: + - OSPFv2 Area ID of the network (A.B.C.D or 0 to 4294967295). + type: str + authentication_type: + description: + - Enable OSPFv2 authentication and its type. + - C(MD5HMAC) - Enable Message digest authentication type. + - C(NONE) - Enable null authentication. + - C(TEXT) - Enable plain text authentication. + type: str + choices: + - 'MD5HMAC' + - 'NONE' + - 'TEXT' + authentication: + description: + - Configure OSPFv2 plain text authentication type password. + - Authentication key shall be max 8 charater long. + type: dict + suboptions: + password: + description: + - Specifies the authentication password. + - Plain text password i.e. password with I(encrypted=false) will be stored in encrypted format in running-config, so idempotency will + not be maintained and hence the task output will always be I(changed=true). + type: str + required: true + encrypted: + description: + - Indicates whether the password is in encrypted format. + type: bool + cost: + description: + - Configure OSPFv2 interface cost (1 to 65535). + type: int + dead_interval: + description: + - Configure OSPFv2 adjacency dead interval (1 to 65535). + type: int + hello_multiplier: + description: + - Minimal 1s dead-interval with fast sub-second hellos. + - Number of Hellos to send each second (1 to 10). + type: int + hello_interval: + description: + - Configure OSPFv2 neighbour hello interval (1 to 65535). + type: int + md_authentication: + description: + - Configure OSPFv2 message digest keys and password. + - Uses MD5 algorithm. + type: list + elements: dict + suboptions: + key_id: + description: + - Specifies the OSPFv2 message digest key ID (1 to 255). + type: int + required: True + md5key: + description: + - Specifies the OSPFv2 message digest password. + - Plain text password i.e. password with I(encrypted=false) will be stored in encrypted format in running-config, so idempotency will + not be maintained and hence the task output will always be I(changed=true). + type: str + encrypted: + description: + - Indicates whether the password is in encrypted format. + type: bool + mtu_ignore: + description: + - Disable OSPFv2 MTU mismatch detection. + type: bool + priority: + description: + - Configure OSPFv2 adjacency router priority (0 to 255). + type: int + retransmit_interval: + description: + - Configure OSPFv2 retransmit interval (2 to 65535). + type: int + transmit_delay: + description: + - Configure OSPFv2 transmit delay (1 to 65535). + type: int + bfd: + description: + - Configure OSPFv2 interface BFD. + type: dict + suboptions: + enable: + description: + - Enable BFD support for OSPFv2. + type: bool + required: true + bfd_profile: + description: + - Configure BFD profile. + type: str + network: + description: + - Configure OSPFv2 interface network type + type: str + choices: + - broadcast + - point_to_point + state: + description: + - Specifies the operation to be performed on the OSPFv2 interfaces configured on the device. + - In case of merged, the input configuration will be merged with the existing OSPFv2 interfaces configuration on the device. + - In case of deleted, the existing OSPFv2 interfaces configuration will be removed from the device. + - In case of overridden, all the existing OSPFv2 interfaces configuration will be deleted and the specified input configuration will be installed. + - In case of replaced, the existing OSPFv2 interface configuration on the device will be replaced by the configuration in the playbook for + each interface group configured by the playbook. + type: str + default: merged + choices: ['merged', 'deleted', 'replaced', 'overridden'] +""" +EXAMPLES = """ +# Using deleted + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile2 +# ip ospf cost 30 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network point-to-point +# ip ospf priority 20 +# ip ospf authentication null 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1/Ml24vwe6RSjUUqI+54BdDyDL0eKUezJw= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +# ip ospf authentication null 10.19.119.1 +# ip ospf message-digest-key 10 md5 U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ= encrypted 10.19.119.1 +#! +#interface Eth1/2 +# ip ospf bfd +# ip ospf network point-to-point +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + - name: Delete the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/1' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 30 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'NONE' + authentication: + password: 'pass2' + - address: '10.19.119.1' + bfd: + enable: True + bfd_profile: 'profile2' + network: point_to_point + - name: 'Eth1/2' + bfd: + enable: True + - name: 'Eth1/3' + state: deleted + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +# ip ospf network point-to-point +#! +#interface Eth1/3 +#! +#sonic# + + +# Using deleted + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile2 +# ip ospf cost 30 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network point-to-point +# ip ospf priority 20 +# ip ospf authentication null 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1/Ml24vwe6RSjUUqI+54BdDyDL0eKUezJw= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +# ip ospf authentication null 10.19.119.1 +# ip ospf message-digest-key 10 md5 U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ= encrypted 10.19.119.1 +#! +#interface Eth1/2 +# ip ospf bfd +# ip ospf network point-to-point +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + - name: Delete the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/1' + - name: 'Eth1/2' + bfd: + enable: True + - name: 'Eth1/3' + state: deleted + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +#! +#interface Eth1/2 +# ip ospf network point-to-point +#! +#interface Eth1/3 +#! +#sonic# + + +# Using merged + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +#! +#sonic# + + - name: Add the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/1' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'password' + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: 'Eth1/3' + ospf_attributes: + - area_id: '3.3.3.3' + address: '10.19.120.2' + authentication_type: 'MD5HMAC' + authentication: + password: 'password' + hello_multiplier: 5 + bfd: + enable: True + network: point_to_point + state: merged + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile1 +# ip ospf cost 20 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication message-digest 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1+ozJSEI69XJb2KR9Pu1Sa3Ou6ujTRalbQ= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + +# Using merged + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile1 +# ip ospf cost 20 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication message-digest 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1+ozJSEI69XJb2KR9Pu1Sa3Ou6ujTRalbQ= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + - name: Add the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/1' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 30 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'NONE' + authentication: + password: 'pass2' + - address: '10.19.119.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ=' + encrypted: True + bfd: + enable: True + bfd_profile: 'profile2' + network: point_to_point + - name: 'Eth1/2' + bfd: + enable: True + network: point_to_point + state: merged + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile2 +# ip ospf cost 30 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network point-to-point +# ip ospf priority 20 +# ip ospf authentication null 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1/Ml24vwe6RSjUUqI+54BdDyDL0eKUezJw= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +# ip ospf authentication null 10.19.119.1 +# ip ospf message-digest-key 10 md5 U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ= encrypted 10.19.119.1 +#! +#interface Eth1/2 +# ip ospf bfd +# ip ospf network point-to-point +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + +# Using replaced + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile1 +# ip ospf cost 20 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication message-digest 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1+ozJSEI69XJb2KR9Pu1Sa3Ou6ujTRalbQ= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + - name: Replace the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/3' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 30 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'NONE' + authentication: + password: 'pass2' + - address: '10.19.119.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ=' + encrypted: True + bfd: + enable: True + bfd_profile: 'profile2' + network: broadcast + state: replaced + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile1 +# ip ospf cost 20 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication message-digest 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1+ozJSEI69XJb2KR9Pu1Sa3Ou6ujTRalbQ= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile2 +# ip ospf cost 30 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication null 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX186k2R2hUXaDloW8hfkApn5Zx5hCQy9usc= encrypted 10.10.120.1 +# ip ospf authentication null 10.19.119.1 +# ip ospf message-digest-key 10 md5 U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ= encrypted 10.19.119.1 +#! +#sonic# + + +# Using overridden + +# Before state: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile1 +# ip ospf cost 20 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication message-digest 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX1+ozJSEI69XJb2KR9Pu1Sa3Ou6ujTRalbQ= encrypted 10.10.120.1 +# ip ospf dead-interval minimal hello-multiplier 5 10.10.120.1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf bfd +# ip ospf network point-to-point +# ip ospf area 3.3.3.3 10.19.120.2 +# ip ospf authentication message-digest 10.19.120.2 +# ip ospf authentication-key U2FsdGVkX19HqGCcf2pzGur9MDnb0VzLNRvoFij3Os0= encrypted 10.19.120.2 +# ip ospf dead-interval minimal hello-multiplier 5 10.19.120.2 +#! +#sonic# + + - name: Override the OSPFv2_interface configurations + sonic_ospfv2_interfaces: + config: + - name: 'Eth1/3' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 30 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'NONE' + authentication: + password: 'pass2' + - address: '10.19.119.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ=' + encrypted: True + bfd: + enable: True + bfd_profile: 'profile2' + network: broadcast + state: overridden + +# After state: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +#! +#interface Eth1/2 +#! +#interface Eth1/3 +# ip ospf area 2.2.2.2 +# ip ospf bfd +# ip ospf bfd profile profile2 +# ip ospf cost 30 +# ip ospf dead-interval 40 +# ip ospf hello-interval 10 +# ip ospf mtu-ignore +# ip ospf network broadcast +# ip ospf priority 20 +# ip ospf authentication null 10.10.120.1 +# ip ospf authentication-key U2FsdGVkX186k2R2hUXaDloW8hfkApn5Zx5hCQy9usc= encrypted 10.10.120.1 +# ip ospf authentication null 10.19.119.1 +# ip ospf message-digest-key 10 md5 U2FsdGVkX1/Bq/+x8a3fsBo9ZrAX56ynmPKnRM87kfQ= encrypted 10.19.119.1 +#! +#sonic# + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after(generated): + description: The generated configuration model invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ospfv2_interfaces.ospfv2_interfaces import Ospfv2_interfacesArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospfv2_interfaces.ospfv2_interfaces import Ospfv2_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Ospfv2_interfacesArgs.argument_spec, + supports_check_mode=True) + + result = Ospfv2_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/sonic_route_maps.py b/plugins/modules/sonic_route_maps.py index d8f11dd8f..b4c92ba11 100644 --- a/plugins/modules/sonic_route_maps.py +++ b/plugins/modules/sonic_route_maps.py @@ -66,7 +66,7 @@ - deny sequence_num: description: - - unique number in the range 1-66535 to specify priority of the map + - "unique number in the range 1-66535 to specify priority of the map" - This value is required for creation and modification of a route - map or route map attributes as well as for deletion of route map - attributes. It can be omitted only when requesting deletion of all @@ -94,12 +94,12 @@ suboptions: default_route: description: - - Default EVPN type-5 route + - "Default EVPN type-5 route" type: bool route_type: description: - "Non-default route type: One of the following:" - - mac-ip route, EVPN Type 3 Inclusive Multicast Ethernet + - "mac-ip route, EVPN Type 3 Inclusive Multicast Ethernet" - Tag (IMET) route, or prefix route type: str choices: @@ -109,7 +109,7 @@ vni: description: - VNI ID to be checked for a match; specified by a value in the - - range 1-16777215 + - "range 1-16777215" type: int ext_comm: description: @@ -120,7 +120,7 @@ description: - Next hop interface name (type and number) to be checked for a - match with the target route. The interface type can be any - - of the following; 'Ethernet/Eth' interface or sub-interface, + - "of the following; 'Ethernet/Eth' interface or sub-interface," - "'Loopback' interface, 'PortChannel' interface or" - "sub-interface, 'Vlan' interface." type: str @@ -137,7 +137,7 @@ type: str next_hop: description: - - name of a prefix list containing a list of next-hop + - "name of a prefix list containing a list of next-hop" - prefixes to be checked for a match with the target route type: str ipv6: @@ -154,13 +154,13 @@ required: true local_preference: description: - - local-preference value to be checked for a match with the - - target route. This is a value in the range 0-4294967295. + - "local-preference value to be checked for a match with the" + - "target route. This is a value in the range 0-4294967295." type: int metric: description: - metric value to be checked for a match with the target route. - - This is a value in the range 0-4294967295. + - "This is a value in the range 0-4294967295." type: int origin: description: @@ -172,7 +172,7 @@ - incomplete peer: description: - - BGP routing peer/neighbor required for a matching route. + - BGP routing peer/neighbor required for a matching route - I(ip), I(ipv6), and I(interface) are mutually exclusive. type: dict suboptions: @@ -184,9 +184,9 @@ type: str interface: description: - - Name (type and number) of a BGP peer interface. + - Name (type and number) of a BGP peer interface - Allowed interface types are Ethernet or Eth (depending - - on the configured interface-naming mode), + - "on the configured interface-naming mode)," - Vlan, and Portchannel type: str source_protocol: @@ -203,17 +203,17 @@ tag: description: - Tag value required for a matching route - - The value must be in the range 1-4294967295 + - "The value must be in the range 1-4294967295" type: int set: - description: Information to set into a matching route for re-distribution + description: "Information to set into a matching route for re-distribution" type: dict suboptions: as_path_prepend: description: - - String specifying a comma-separated list of AS-path numbers + - "String specifying a comma-separated list of AS-path numbers" - "to prepend to the BGP AS-path attribute in a matched route." - - AS-path values in the list must be in the range + - "AS-path values in the list must be in the range" - "1-4294967295; for example, 2000,3000" type: str comm_list_delete: @@ -328,13 +328,13 @@ - Set the corresponding attribute into a matching route - if the value of this Ansible attribute is 'true'. - The attribute indicates that the routing algorithm must - - prefer the global next-hop address over the link-local + - "prefer the global next-hop address over the link-local" - address if both exist. type: bool local_preference: description: - "BGP local preference path attribute; integer value in" - - the range 0-4294967295 + - "the range 0-4294967295" type: int metric: description: @@ -345,7 +345,7 @@ value: description: - "metric value to be set into a matching route;" - - value in the range 0-4294967295 + - "value in the range 0-4294967295" type: int rtt_action: description: @@ -375,8 +375,13 @@ - incomplete weight: description: - - BGP weight for the routing table. The weight must be an - - integer in the range 0-4294967295 + - "BGP weight to be set for a matching route: The weight must be" + - "an integer in the range 0-4294967295" + type: int + tag: + description: + - Tag value to be set for a matching route + - "The value must be in the range 1-4294967295" type: int call: description: @@ -467,6 +472,7 @@ metric_value: 870 origin: egp weight: 93471 + tag: 65 - map_name: rm1 action: deny sequence_num: 3047 @@ -544,6 +550,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match evpn route-type multicast @@ -605,6 +612,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match evpn route-type multicast @@ -724,6 +732,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -795,6 +804,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ------------ - name: Replace a list dellemc.enterprise_sonic.sonic_route_maps: @@ -843,6 +853,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # Using "replaced" state to replace the contents of dictionaries @@ -880,6 +891,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -971,6 +983,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -1039,6 +1052,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -1164,6 +1178,7 @@ rtt_action: add origin: egp weight: 93471 + tag: 65 - map_name: rm1 action: deny sequence_num: 3047 @@ -1256,6 +1271,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -1327,6 +1343,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ------------ - name: Delete selected route map configuration dellemc.enterprise_sonic.sonic_route_maps: @@ -1381,6 +1398,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # Using "deleted" state to remove a route map or route map subset @@ -1413,6 +1431,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm1 deny 3047 # match as-path bgp_as3 @@ -1447,13 +1466,12 @@ # match source-protocol static # set metric -rtt # ------------ -- name: Delete a route map or route map subset +- name: Delete a route map subset or a route map dellemc.enterprise_sonic.sonic_route_maps: config: - map_name: rm1 sequence_num: 3047 - map_name: rm2 - sequence_num: 100 state: deleted # After state: @@ -1484,6 +1502,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm3 deny 285 # match evpn route-type macip @@ -1532,6 +1551,7 @@ # set local-preference 635 # set origin egp # set weight 93471 +# set tag 65 # ! # route-map rm3 deny 285 # match evpn route-type macip diff --git a/plugins/modules/sonic_system.py b/plugins/modules/sonic_system.py index 7c4014641..5d249724f 100644 --- a/plugins/modules/sonic_system.py +++ b/plugins/modules/sonic_system.py @@ -83,6 +83,31 @@ choices: - ENABLE - DISABLE + load_share_hash_algo: + description: + - Specifies different types of ECMP Load share hash algorithm + version_added: 2.5.0 + type: str + choices: + - CRC + - XOR + - CRC_32LO + - CRC_32HI + - CRC_CCITT + - CRC_XOR + - JENKINS_HASH_LO + - JENKINS_HASH_HI + audit_rules: + description: + - Specifies audit rule profile type. + - Can be used on SONiC release versions 4.4.0 and above. + version_added: 2.5.0 + type: str + choices: + - BASIC + - DETAIL + - CUSTOM + - NONE state: description: - Specifies the operation to be performed on the system parameters configured on the device. @@ -104,6 +129,7 @@ #ip anycast-address enable #ipv6 anycast-address enable #interface-naming standard +#ip load-share hash algorithm JENKINS_HASH_HI - name: Merge provided configuration with device configuration dellemc.enterprise_sonic.sonic_system: @@ -112,6 +138,7 @@ interface_naming: standard anycast_address: ipv6: true + load_share_hash_algo: JENKINS_HASH_HI state: deleted # After state: @@ -134,6 +161,7 @@ #ip anycast-address enable #ipv6 anycast-address enable #interface-naming standard +#ip load-share hash algorithm JENKINS_HASH_HI - name: Delete all system related configs in device configuration dellemc.enterprise_sonic.sonic_system: @@ -164,6 +192,7 @@ ipv6: true ipv4: true mac_address: aa:bb:cc:dd:ee:ff + load_share_hash_algo: JENKINS_HASH_HI state: merged # After state: @@ -175,6 +204,7 @@ #ip anycast-address enable #ipv6 anycast-address enable #interface-naming standard +#ip load-share hash algorithm JENKINS_HASH_HI # Using replaced # @@ -219,6 +249,7 @@ anycast_address: ipv6: true ipv4: true + load_share_hash_algo: JENKINS_HASH_HI state: replaced # After state: @@ -229,6 +260,7 @@ #ip anycast-address enable #ipv6 anycast-address enable #interface-naming standard +#ip load-share hash algorithm JENKINS_HASH_HI # Using overridden # @@ -240,6 +272,7 @@ #ip anycast-mac-address aa:bb:cc:dd:ee:ff #ip anycast-address enable #ipv6 anycast-address enable +#ip load-share hash algorithm JENKINS_HASH_HI - name: Override system configuration. sonic_system: @@ -249,6 +282,7 @@ anycast_address: ipv4: true mac_address: bb:aa:cc:dd:ee:ff + load_share_hash_algo: CRC_XOR state: overridden # After state: @@ -259,6 +293,7 @@ #ip anycast-mac-address bb:aa:cc:dd:ee:ff #ip anycast-address enable #interface-naming standard +#ip load-share hash algorithm CRC_XOR # Using merged # @@ -274,6 +309,8 @@ hostname: SONIC interface_naming: standard auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_HI + audit_rules: BASIC state: merged # After state: @@ -284,6 +321,8 @@ #hostname SONIC #interface-naming standard #auto-breakout +#ip load-share hash algorithm JENKINS_HASH_HI +#auditd-system rules basic # Using deleted # @@ -295,12 +334,16 @@ #hostname SONIC #interface-naming standard #auto-breakout +#ip load-share hash algorithm JENKINS_HASH_HI +#auditd-system rules basic - name: Delete auto-breakout configuration on the device dellemc.enterprise_sonic.sonic_system: config: hostname: SONIC auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_HI + audit_rules: BASIC state: deleted # After state: diff --git a/plugins/modules/sonic_vlan_mapping.py b/plugins/modules/sonic_vlan_mapping.py index 53595fd76..6f8d965d3 100644 --- a/plugins/modules/sonic_vlan_mapping.py +++ b/plugins/modules/sonic_vlan_mapping.py @@ -53,7 +53,8 @@ type: str mapping: description: - - Defining a single vlan mapping. + - Define vlan mappings. + - dot1q_tunnel and vlan_translation are mutually exclusive. type: list elements: dict suboptions: @@ -63,30 +64,74 @@ - VLAN ID range is 1-4094. required: true type: int - vlan_ids: - description: - - Configure customer VLAN IDs. - - If mode is double tagged translation then this VLAN ID represents the outer VLAN ID. - - If mode is set to stacking can pass ranges and/or multiple list entries. - - Individual VLAN ID or (-) separated range of VLAN IDs. - type: list - elements: str dot1q_tunnel: description: - - Specify whether it is a vlan stacking or translation (false means translation; true means stacking). - type: bool - default: false - inner_vlan: - description: - - Configure inner customer VLAN ID. - - VLAN IDs range is 1-4094. - - Only available for double tagged translations. - type: int - priority: + - Specify a vlan stacking. + type: dict + suboptions: + vlan_ids: + description: + - Configure customer VLAN IDs. + - It can pass ranges and/or multiple list entries. + - Individual VLAN ID or (-) separated range of VLAN IDs. + type: list + elements: str + priority: + description: + - Set priority level of the vlan stacking. + - Priority range is 0-7. + type: int + vlan_translation: description: - - Set priority level of the vlan mapping. - - Priority range is 0-7. - type: int + - Specify a vlan translation. + version_added: '3.0.0' + type: dict + suboptions: + multi_tag: + description: + - Indicate if there are multiple tags. + type: bool + match_single_tags: + description: + - Configure single tagged vlan translation. + type: list + elements: dict + suboptions: + outer_vlan: + description: + - Configure outer customer VLAN ID. + - VLAN ID range is 1-4094. + required: true + type: int + priority: + description: + - Set priority level of the vlan translation. + - Priority range is 0-7. + type: int + match_double_tags: + description: + - Configure double tagged vlan translation. + type: list + elements: dict + suboptions: + inner_vlan: + description: + - Configure inner customer VLAN ID. + - VLAN ID range is 1-4094. + - Only available for double tagged translations. + required: true + type: int + outer_vlan: + description: + - Configure outer customer VLAN ID. + - VLAN ID range is 1-4094. + required: true + type: int + priority: + description: + - Set priority level of the vlan translation. + - Priority range is 0-7. + type: int state: description: - Specifies the operation to be performed on the vlan mappings configured on the device. @@ -103,393 +148,228 @@ - overridden """ EXAMPLES = """ +# # Using deleted # -# Before State: +# Before state: # ------------- # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -# switchport vlan-mapping 392 inner 590 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-432 dot1q-tunnel 2436 priority 3 -# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3 -#! - - - - name: Delete vlan mapping configurations - sonic_vlan_mapping: - config: - - name: Ethernet8 - mapping: - - service_vlan: 2755 - - name: Ethernet16 - mapping: - - service_vlan: 2567 - priority: 3 - - service_vlan: 2436 - vlan_ids: - - 404 - - 401 - - 412 - - 430-431 - priority: 3 - state: deleted - -# After State: +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 360-366,392 2755 3 +# +# - name: Delete dot1q_tunnel configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet4 +# mapping: +# - service_vlan: 2755 +# dot1q_tunnel: +# vlan_ids: +# - 392 +# - 360-362 +# state: deleted +# +# After state: # ------------ # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400,402,406,408,410,420,422,432 dot1q-tunnel 2436 -# switchport vlan-mapping 300 dot1q-tunnel 2567 -#! - - +#onic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 363-366 2755 3 +# # Using deleted # -# Before State: +# Before state: # ------------- # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -# switchport vlan-mapping 392 inner 590 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436 -# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3 -#! - - - - name: Delete vlan mapping configurations - sonic_vlan_mapping: - config: - - name: Ethernet8 - - name: Ethernet16 - mapping: - - service_vlan: 2567 - state: deleted - -# After State: +#sonic# show interface vlan-mappings +#Flags: M - Multi-tag +#--------------------------------------------------------- +#Name Outer Inner Mapped Vlan Priority Flags +#--------------------------------------------------------- +#Ethernet8 610 600 2567 - - +#Ethernet8 611 601 2567 1 - +#Ethernet8 612 602 2567 2 - +# +# - name: Delete vlan translation configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet8 +# mapping: +# - service_vlan: 2567 +# vlan_translation: +# match_double_tags: +# - inner_vlan: 602 +# outer_vlan: 612 +# priority: 2 +# state: deleted +# +# After state: # ------------ # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdo#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,406,408,410,420,422,431 dot1q-tunnel 2436 -#! - - +#sonic# show interface vlan-mappings +#Flags: M - Multi-tag +#--------------------------------------------------------- +#Name Outer Inner Mapped Vlan Priority Flags +#--------------------------------------------------------- +#Ethernet8 610 600 2567 - - +#Ethernet8 611 601 2567 1 - +# +# # Using merged # -# Before State: +# Before state: # ------------- # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -#! -#interface PortChannel 2 -# switchport vlan-mapping 345 2999 priority 0 -# switchport vlan-mapping 500,540 dot1q-tunnel 3000 -# no shutdown -#! - - - name: Add vlan mapping configurations - sonic_vlan_mapping: - config: - - name: Ethernet8 - mapping: - - service_vlan: 2755 - vlan_ids: - - 392 - dot1q_tunnel: false - inner_vlan: 590 - - name: Ethernet16 - mapping: - - service_vlan: 2567 - vlan_ids: - - 300 - dot1q_tunnel: true - priority: 3 - - service_vlan: 2436 - vlan_ids: - - 400-402 - - 404 - - 406 - - 408 - - 410 - - 412 - - 420 - - 422 - - 430-431 - dot1q_tunnel: true - - name: Portchannel 2 - mapping: - - service_vlan: 2999 - priority: 4 - - service_vlan: 3000 - vlan_ids: - - 506-512 - - 561 - priority: 5 - state: merged - -# After State: +#sonic# show interface vlan-mappings dot1q-tunnel +# +# - name: Merge dot1q_tunnel configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet4 +# mapping: +# - service_vlan: 2755 +# dot1q_tunnel: +# vlan_ids: +# - 392 +# - 360-366 +# priority: 3 +# state: merged +# +# After state: # ------------ # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -# switchport vlan-mapping 392 inner 590 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436 -# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3 -#! -#interface PortChannel 2 -# switchport vlan-mapping 345 2999 priority 4 -# switchport vlan-mapping 500,506-512,540,561 dot1q-tunnel 3000 priority 5 -# no shutdown -#! - - +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 360-366,392 2755 3 +# +# Using merged +# +# Before state: +# ------------- +# +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 360-366,392 2755 3 +# +# - name: Merge vlan translation configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet8 +# mapping: +# - service_vlan: 2567 +# vlan_translation: +# match_double_tags: +# - inner_vlan: 600 +# outer_vlan: 610 +# - inner_vlan: 601 +# outer_vlan: 611 +# priority: 1 +# - inner_vlan: 602 +# outer_vlan: 612 +# priority: 2 +# state: merged +# +# After state: +# ------------ +# +#sonic# show interface vlan-mappings +#Flags: M - Multi-tag +#--------------------------------------------------------- +#Name Outer Inner Mapped Vlan Priority Flags +#--------------------------------------------------------- +#Ethernet8 610 600 2567 - - +#Ethernet8 611 601 2567 1 - +#Ethernet8 612 602 2567 2 - +# +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 360-366,392 2755 3 +# +# # Using replaced # -# Before State: +# Before state: # ------------- # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -# switchport vlan-mapping 392 inner 590 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436 -# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3 -#! -#interface PortChannel 2 -# switchport vlan-mapping 345 2999 priority 0 -# no shutdown -#! - - - name: Replace vlan mapping configurations - sonic_vlan_mapping: - config: - - name: Ethernet8 - mapping: - - service_vlan: 2755 - vlan_ids: - - 390 - dot1q_tunnel: false - inner_vlan: 593 - - name: Ethernet16 - mapping: - - service_vlan: 2567 - vlan_ids: - - 310 - - 330-340 - priority: 5 - - name: Portchannel 2 - mapping: - - service_vlan: 2999 - vlan_ids: - - 345 - dot1q_tunnel: true - priority: 1 - state: replaced - - -# After State: +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 360-366,392 2755 3 +# +# - name: Replace dot1q_tunnel configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet4 +# mapping: +# - service_vlan: 2755 +# dot1q_tunnel: +# vlan_ids: +# - 660-666 +# priority: 6 +# state: replaced +# +# After state: # ------------ # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -# switchport vlan-mapping 390 inner 593 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436 -# switchport vlan-mapping 310,330-340 dot1q-tunnel 2567 priority 5 -#! -#interface PortChannel 2 -# switchport vlan-mapping 345 dot1q_tunnel 2999 priority 1 -# no shutdown -#! - - +#sonic# show interface vlan-mappings dot1q-tunnel +#-------------------------------------------------------------------- +#Name Vlan dot1q-tunnel Vlan Priority +#-------------------------------------------------------------------- +#Ethernet4 660-666 2755 6 +# # Using overridden # -# Before State: +# Before state: # ------------- # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 623 2411 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436 -#! - - - name: Override the vlan mapping configurations - sonic_vlan_mapping: - config: - - name: Ethernet8 - mapping: - - service_vlan: 2755 - vlan_ids: - - 392 - dot1q_tunnel: false - inner_vlan: 590 - - name: Ethernet16 - mapping: - - service_vlan: 2567 - vlan_ids: - - 300 - dot1q_tunnel: true - priority: 3 - - name: Portchannel 2 - mapping: - - service_vlan: 2999 - vlan_ids: - - 345 - priority: 0 - state: overridden - -# After State: +#sonic# show interface vlan-mappings +#Flags: M - Multi-tag +#--------------------------------------------------------- +#Name Outer Inner Mapped Vlan Priority Flags +#--------------------------------------------------------- +#Ethernet8 610 600 2567 - - +#Ethernet8 611 601 2567 1 - +#Ethernet8 612 602 2567 2 - +# +# - name: Override vlan translation configuration +# sonic_vlan_mapping: +# config: +# - name: Ethernet8 +# mapping: +# - service_vlan: 2567 +# vlan_translation: +# match_double_tags: +# - inner_vlan: 701 +# outer_vlan: 711 +# priority: 5 +# - inner_vlan: 702 +# outer_vlan: 712 +# priority: 6 +# state: overridden +# +# After state: # ------------ # -#sonic# show running-configuration interface -#! -#interface Ethernet8 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 392 inner 590 2755 -#! -#interface Ethernet16 -# mtu 9100 -# speed 400000 -# fec RS -# unreliable-los auto -# shutdown -# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3 -#! -#interface PortChannel 2 -# switchport vlan-mapping 345 2999 priority 0 -# no shutdown -#! - - +#sonic# show interface vlan-mappings +#Flags: M - Multi-tag +#--------------------------------------------------------- +#Name Outer Inner Mapped Vlan Priority Flags +#--------------------------------------------------------- +#Ethernet8 711 701 2567 5 - +#Ethernet8 712 702 2567 6 - +# """ RETURN = """ before: diff --git a/plugins/modules/sonic_vrrp.py b/plugins/modules/sonic_vrrp.py new file mode 100644 index 000000000..aaed82ae5 --- /dev/null +++ b/plugins/modules/sonic_vrrp.py @@ -0,0 +1,563 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_vrrp +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: sonic_vrrp +author: "Santhosh Kumar T(@santhosh-kt)" +version_added: "2.5.0" +short_description: Configure VRRP protocol settings on SONiC. +description: + - This module provides configuration management of VRRP protocol settings on devices running SONiC + - Configure interface IP address before configuring VRRP + - Configure interface VRF forwarding before configuring VRRP in a VRF +options: + config: + description: + - Specifies the VRRP related configuration. + type: list + elements: dict + suboptions: + name: + description: + - Full name of the Layer 3 interface, i.e. Eth1/1. + required: true + type: str + group: + description: + - Defining the VRRP/VRRP6 group + type: list + elements: dict + suboptions: + virtual_router_id: + description: + - VRRP ID (1 to 255) + type: int + required: true + afi: + description: + - VRRP configurations to be set for the interface mentioned in types(VRRP/VRRP6). + type: str + required: true + choices: + - ipv4 + - ipv6 + virtual_address: + description: + - Configure virtual IP Address. + type: list + elements: dict + suboptions: + address: + description: + - List of IP addresses to be set. + type: str + advertisement_interval: + description: + - Configure advertisement interval (1 to 254) + type: int + preempt: + description: + - Enable preempt + type: bool + priority: + description: + - Priority for MASTER election (1 to 254) + type: int + track_interface: + description: + - Configure track interface for priority change. + - I(interface) and I(priority_increment) are required together. + type: list + elements: dict + suboptions: + interface: + description: + - Full name of the Layer 3 interface, i.e. Eth1/1. + type: str + required: true + priority_increment: + description: + - Weight for changing priority (1 to 254) + type: int + use_v2_checksum: + description: + - Enable checksum compatibility with VRRPv2 (Not supported for IPv6). + type: bool + version: + description: + - Configure VRRP Version 2 or 3 (Not supported for IPv6). + type: int + choices: + - 2 + - 3 + state: + description: + - Specifies the operation to be performed on the VRRP process configured on the device. + - In case of merged, the input configuration will be merged with the existing VRRP configuration on the device. + - In case of deleted, the existing VRRP configuration will be removed from the device. + - In case of overridden, all existing VRRP configuration will be deleted and the specified input configuration will be installed. + - In case of replaced, the existing VRRP configuration on the device will be replaced by the configuration in the + playbook for each VRRP interface/group configured by the playbook. + default: merged + type: str + choices: ['merged', 'deleted','replaced', 'overridden'] +""" +EXAMPLES = """ +# Using deleted +# +# Before State: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# preempt +# vip 81.1.1.3 +# vip 81.1.1.4 +# ! +# vrrp 10 address-family ipv6 +# priority 10 +# advertisement-interval 4 +# vip 81::3 +# vip 81::4 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# priority 20 +# vip 61.1.1.3 +# ! +# vrrp 15 address-family ipv4 +# priority 20 +# preempt +# vip 61.1.1.4 +#! + - name: Delete VRRP and VRRP6 relay configurations + sonic_vrrp: + config: + - name: 'Eth1/1' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 81.1.1.4 + preempt: true + - virtual_router_id: 10 + afi: ipv6 + advertisement_interval: 4 + priority: 10 + - name: 'Eth1/3' + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 61.1.1.3 + priority: 20 + - virtual_router_id: 15 + afi: ipv4 + state: deleted +# After State: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# vip 81.1.1.3 +# ! +# vrrp 10 address-family ipv6 +# vip 81::3 +# vip 81::4 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +#! + +# Using merged +# +# Before State: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +#! + - name: Add VRRP and VRRP6 configurations + sonic_vrrp: + config: + - name: 'Eth1/1' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 81.1.1.3 + - address: 81.1.1.4 + preempt: true + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 81::3 + - address: 81::4 + advertisement_interval: 4 + priority: 10 + - name: 'Eth1/3' + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 61.1.1.3 + priority: 20 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 61.1.1.4 + preempt: true + priority: 20 + state: merged +# After State: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# preempt +# vip 81.1.1.3 +# vip 81.1.1.4 +# ! +# vrrp 10 address-family ipv6 +# priority 10 +# advertisement-interval 4 +# vip 81::3 +# vip 81::4 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# priority 20 +# vip 61.1.1.3 +# ! +# vrrp 15 address-family ipv4 +# priority 20 +# preempt +# vip 61.1.1.4 +#! + +# Using replaced +# +# Before State: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# preempt +# vip 81.1.1.3 +# vip 81.1.1.4 +# ! +# vrrp 10 address-family ipv6 +# priority 10 +# advertisement-interval 4 +# vip 81::3 +# vip 81::4 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# priority 20 +# vip 61.1.1.3 +# ! +# vrrp 15 address-family ipv4 +# priority 20 +# preempt +# vip 61.1.1.4 +#! + - name: Replace VRRP and VRRP6 relay configurations + sonic_vrrp: + config: + - name: 'Eth1/1' + group: + - virtual_router_id: 10 + afi: ipv6 + priority: 20 + - name: 'Eth1/3' + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 61.1.1.5 + preempt: false + track_interface: + - interface: Eth1/1 + priority_increment: 10 + state: replaced +# After State: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# vip 81.1.1.3 +# vip 81.1.1.4 +# ! +# vrrp 10 address-family ipv6 +# priority 20 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# no preempt +# vip 61.1.1.5 +# track-interface Eth1/1 weight 10 +# ! +# vrrp 15 address-family ipv4 +# priority 20 +# preempt +# vip 61.1.1.4 +#! + +# Using overridden +# +# Before State: +# ------------- +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 1 address-family ipv4 +# preempt +# vip 81.1.1.3 +# vip 81.1.1.4 +# ! +# vrrp 10 address-family ipv6 +# priority 10 +# advertisement-interval 4 +# vip 81::3 +# vip 81::4 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# priority 20 +# vip 61.1.1.3 +# ! +# vrrp 15 address-family ipv4 +# priority 20 +# preempt +# vip 61.1.1.4 +#! + - name: Overwrite the VRRP and VRRP6 relay configurations + sonic_vrrp: + config: + - name: 'Eth1/1' + group: + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 81.1.1.15 + preempt: false + - name: 'Eth1/3' + group: + - virtual_router_id: 5 + afi: ipv4 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 61.1.1.5 + state: overridden +# After State: +# ------------ +# +#sonic# show running-configuration interface +#! +#interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 81.1.1.1/24 +# ipv6 address 81::1/24 +# ! +# vrrp 15 address-family ipv4 +# no preempt +# vip 81.1.1.15 +#! +#interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ip address 61.1.1.1/24 +# ! +# vrrp 5 address-family ipv4 +# ! +# vrrp 15 address-family ipv4 +# vip 61.1.1.5 +#! +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after(generated): + description: The generated configuration model invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.vrrp.vrrp import VrrpArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vrrp.vrrp import Vrrp + + +def main(): + """ + Main entry point for module execution + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=VrrpArgs.argument_spec, + supports_check_mode=True) + + result = Vrrp(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/regression/hosts b/tests/regression/hosts index f2fd8da12..eb3fd75ff 100644 --- a/tests/regression/hosts +++ b/tests/regression/hosts @@ -8,6 +8,6 @@ sonic2 [datacenter:vars] ansible_network_os=dellemc.enterprise_sonic.sonic -ansible_python_interpreter=/usr/bin/python3.9 +ansible_python_interpreter=/usr/bin/python3.11 ansible_httpapi_use_ssl=true ansible_httpapi_validate_certs=false diff --git a/tests/regression/roles/sonic_aaa/defaults/main.yml b/tests/regression/roles/sonic_aaa/defaults/main.yml index e51a026cd..f87a39824 100644 --- a/tests/regression/roles/sonic_aaa/defaults/main.yml +++ b/tests/regression/roles/sonic_aaa/defaults/main.yml @@ -1,72 +1,213 @@ --- ansible_connection: httpapi module_name: aaa + tests: - name: test_case_01 - description: aaa properties + description: Initial AAA configuration state: merged input: authentication: - data: - fail_through: true - group: tacacs+ - local: true + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - name: test_case_02 - description: Update created aaa properties + description: Modify AAA configuration state: merged input: authentication: - data: - fail_through: false + auth_method: + - local + - tacacs+ + - ldap + - radius + console_auth_local: False + failthrough: False + authorization: + commands_auth_method: + - tacacs+ + - local + login_auth_method: + - ldap + - local + name_service: + group: + - local + - login + netgroup: + - ldap + passwd: + - login + shadow: + - login + - local + - ldap + sudoers: + - local - name: test_case_03 - description: Update aaa properties - change group - state: merged + description: Replace AAA configuration + state: replaced input: authentication: - data: - fail_through: true - group: radius + console_auth_local: True + authorization: + login_auth_method: + - local + name_service: + group: + - login + - name: test_case_04 - description: Replace aaa properties - state: replaced + description: Override AAA configuration + state: overridden input: authentication: - data: - fail_through: false - group: ldap + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - name: test_case_05 - description: Override aaa properties - state: overridden + description: Delete AAA individual attributes + state: deleted input: authentication: - data: - fail_through: true - group: radius - local: true + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - name: test_case_06 - description: Delete aaa properties - state: deleted + description: Add AAA configuration for delete all + state: merged input: authentication: - data: - group: radius + auth_method: + - local + - tacacs+ + - ldap + - radius + console_auth_local: False + failthrough: False + authorization: + commands_auth_method: + - tacacs+ + - local + login_auth_method: + - ldap + - local + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local - name: test_case_07 - description: aaa properties - state: merged - input: - authentication: - data: - fail_through: true - group: radius - local: true - -test_delete_all: - - name: del_all_test_case_01 - description: Delete aaa properties + description: Delete all AAA configuratiom state: deleted + input: {} diff --git a/tests/regression/roles/sonic_aaa/tasks/main.yml b/tests/regression/roles/sonic_aaa/tasks/main.yml index fdcaa9a43..0678e3732 100644 --- a/tests/regression/roles/sonic_aaa/tasks/main.yml +++ b/tests/regression/roles/sonic_aaa/tasks/main.yml @@ -1,17 +1,11 @@ -- debug: msg="sonic_aaa Test started ..." +- debug: msg="sonic_aaa test started ..." -- name: Preparations test +- set_fact: + base_cfg_path: "{{ playbook_dir + '/roles/' + role_name + '/' + 'templates/' }}" + +- name: Preparation tests include_tasks: preparation_tests.yaml - name: "Test {{ module_name }} started ..." include_tasks: tasks_template.yaml loop: "{{ tests }}" - -- name: "test_delete_all {{ module_name }} stated ..." - include_tasks: tasks_template_del.yaml - loop: "{{ test_delete_all }}" - when: test_delete_all is defined - -- name: Display all variables/facts known for a host - debug: - var: hostvars[inventory_hostname].ansible_facts.test_reports diff --git a/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml index e8a964f3f..ee5f19794 100644 --- a/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml +++ b/tests/regression/roles/sonic_aaa/tasks/preparation_tests.yaml @@ -1,4 +1,4 @@ -- name: Deletes old radius server configurations +- name: Delete existing AAA configuration sonic_aaa: config: {} state: deleted diff --git a/tests/regression/roles/sonic_bgp/defaults/main.yml b/tests/regression/roles/sonic_bgp/defaults/main.yml index 04d0a515b..0acbf6115 100644 --- a/tests/regression/roles/sonic_bgp/defaults/main.yml +++ b/tests/regression/roles/sonic_bgp/defaults/main.yml @@ -5,17 +5,30 @@ module_name: bgp vrf_1: VrfReg1 vrf_2: VrfReg2 vrf_3: VrfReg3 +vrf_5: VrfReg5 +vrf_6: VrfReg6 +vrf_7: VrfReg7 bgp_as_1: 51 bgp_as_2: 52 bgp_as_3: 53 bgp_as_4: 54 +bgp_as_5: 6553607 +bgp_as_5_dot: "100.7" +bgp_as_6: 13107209 +bgp_as_6_dot: "200.9" +bgp_as_7: 300 +bgp_as_7_dot: "0.300" preparations_tests: init_vrf: - "ip vrf {{vrf_1}}" - "ip vrf {{vrf_2}}" - "ip vrf {{vrf_3}}" + - "ip vrf {{vrf_5}}" + - "ip vrf {{vrf_6}}" + - "ip vrf {{vrf_7}}" + tests_cli: - name: cli_test_case_01 @@ -70,6 +83,20 @@ tests: router_id: 110.2.2.5 rt_delay: 20 vrf_name: "{{vrf_1}}" + - bgp_as: "{{ bgp_as_5_dot }}" + router_id: 120.2.2.5 + rt_delay: 50 + vrf_name: "{{vrf_5}}" + - bgp_as: "{{ bgp_as_6 }}" + router_id: 130.2.2.6 + rt_delay: 60 + vrf_name: "{{vrf_6}}" + as_notation: "asdot" + - bgp_as: "{{ bgp_as_7 }}" + router_id: 140.2.2.7 + rt_delay: 70 + vrf_name: "{{vrf_7}}" + as_notation: "asdot+" - name: test_case_02 description: Updates BGP properties state: merged @@ -83,6 +110,15 @@ tests: rt_delay: 22 vrf_name: "{{vrf_1}}" log_neighbor_changes: True + - bgp_as: "{{ bgp_as_5_dot }}" + vrf_name: "{{vrf_5}}" + as_notation: "asdot" + - bgp_as: "{{ bgp_as_6 }}" + vrf_name: "{{vrf_6}}" + as_notation: "asdot+" + - bgp_as: "{{ bgp_as_7 }}" + vrf_name: "{{vrf_7}}" + as_notation: "asdot" - name: test_case_03 description: Deletes BGP properties state: deleted @@ -96,6 +132,12 @@ tests: rt_delay: 22 vrf_name: "{{vrf_1}}" log_neighbor_changes: True + - bgp_as: "{{ bgp_as_6 }}" + vrf_name: "{{vrf_6}}" + as_notation: "asdot+" + - bgp_as: "{{ bgp_as_7_dot }}" + vrf_name: "{{vrf_7}}" + as_notation: "asdot" - name: test_case_04 description: creates bestpath BGP properties state: merged @@ -287,6 +329,12 @@ tests: timers: holdtime: 90 keepalive_interval: 30 + - bgp_as: "{{ bgp_as_5 }}" + router_id: 120.2.2.155 + vrf_name: "{{vrf_5}}" + as_notation: "asdot+" + - bgp_as: "{{ bgp_as_6_dot }}" + vrf_name: "{{vrf_6}}" - name: test_case_11 description: Overrides BGP properties state: overridden @@ -312,6 +360,12 @@ tests: multipath_relax_as_set: True med: missing_as_worst: True + - bgp_as: "{{ bgp_as_5 }}" + vrf_name: "{{vrf_5}}" + - bgp_as: "{{ bgp_as_7_dot }}" + router_id: 140.2.2.177 + vrf_name: "{{vrf_7}}" + as_notation: "asdot+" - name: test_case_12 description: Deletes all BGP properties state: deleted diff --git a/tests/regression/roles/sonic_bgp_af/defaults/main.yml b/tests/regression/roles/sonic_bgp_af/defaults/main.yml index 09cbb579d..6d91d110e 100644 --- a/tests/regression/roles/sonic_bgp_af/defaults/main.yml +++ b/tests/regression/roles/sonic_bgp_af/defaults/main.yml @@ -107,6 +107,11 @@ tests: - metric: "25" protocol: static route_map: rmap_reg3 + aggregate_address_config: + - prefix: "1.1.1.1/1" + as_set: True + policy_name: rmap_reg1 + summary_only: True - afi: ipv6 safi: unicast max_path: @@ -122,6 +127,11 @@ tests: - metric: "28" protocol: static route_map: rmap_reg2 + aggregate_address_config: + - prefix: "2::2/2" + as_set: False + policy_name: rmap_reg2 + summary_only: False - afi: l2vpn safi: evpn advertise_pip: True @@ -214,6 +224,11 @@ tests: - metric: "35" protocol: static route_map: rmap_reg3 + aggregate_address_config: + - prefix: "3.3.3.3/3" + as_set: False + policy_name: rmap_reg3 + summary_only: False - afi: ipv6 safi: unicast max_path: @@ -229,6 +244,11 @@ tests: - metric: "38" protocol: static route_map: rmap_reg2 + aggregate_address_config: + - prefix: "4::4/4" + as_set: True + policy_name: rmap_reg3 + summary_only: True - afi: l2vpn safi: evpn advertise_pip_ip: "3.3.3.3" @@ -335,6 +355,12 @@ tests: - metric: "30" protocol: connected route_map: rmap_reg1 + aggregate_address_config: + - prefix: "1.1.1.1/1" + as_set: True + policy_name: rmap_reg1 + summary_only: True + - prefix: "3.3.3.3/3" - afi: ipv6 safi: unicast max_path: @@ -506,6 +532,15 @@ tests: network: - '10.1.1.0/24' - '10.1.2.0/24' + aggregate_address_config: + - prefix: "5.5.5.5/5" + as_set: True + policy_name: rmap_reg1 + summary_only: True + - prefix: "9.9.9.9/9" + as_set: False + policy_name: rmap_reg2 + summary_only: False - bgp_as: "{{ bgp_as_4 }}" vrf_name: "{{vrf_3}}" address_family: @@ -563,6 +598,10 @@ tests: network: - '10.1.1.1/24' - '11.1.1.1/24' + aggregate_address_config: + - prefix: "5.5.5.5/5" + - prefix: "9.9.9.9/9" + as_set: True - afi: ipv6 safi: unicast redistribute: @@ -573,6 +612,8 @@ tests: network: - '1::1/64' - '2::1/64' + aggregate_address_config: + - prefix: "7::7/7" - bgp_as: "{{bgp_as_3}}" vrf_name: "{{vrf_2}}" address_family: @@ -660,6 +701,11 @@ tests: - VrfReg1 - VrfReg2 route_map: rmap_reg2 + aggregate_address_config: + - prefix: "1::1/1" + as_set: True + policy_name: rmap_reg1 + summary_only: True - name: test_case_11 description: Delete3 BGP AF properties state: deleted diff --git a/tests/regression/roles/sonic_bgp_af/tasks/main.yml b/tests/regression/roles/sonic_bgp_af/tasks/main.yml index a7ae56fcc..98def2280 100644 --- a/tests/regression/roles/sonic_bgp_af/tasks/main.yml +++ b/tests/regression/roles/sonic_bgp_af/tasks/main.yml @@ -1,4 +1,4 @@ -- debug: msg="sonic_interfaces Test started ..." +- debug: msg="sonic_bgp_af Test started ..." - name: Preparations test, creates VLANs include_tasks: preparation_tests.yaml diff --git a/tests/regression/roles/sonic_bgp_neighbors/defaults/main.yml b/tests/regression/roles/sonic_bgp_neighbors/defaults/main.yml index dcc2b657f..1dfcec9b3 100644 --- a/tests/regression/roles/sonic_bgp_neighbors/defaults/main.yml +++ b/tests/regression/roles/sonic_bgp_neighbors/defaults/main.yml @@ -3,10 +3,22 @@ ansible_connection: httpapi module_name: sonic_bgp_neighbors bgp_as_1: 51 -bgp_as_2: 52 +bgp_as_5: 53 +bgp_as_6: 54 vrf_1: VrfReg1 vrf_2: VrfReg2 +vrf_5: VrfReg5 +vrf_6: VrfReg6 + +peer_as_5: 6553607 +peer_as_5_dot: "100.7" +local_as_5: 6553602 +local_as_5_dot: "100.2" +peer_as_6: 300 +peer_as_6_dot: "0.300" +local_as_6: 13107209 +local_as_6_dot: "200.9" preparations_tests: init_route_map: @@ -21,6 +33,8 @@ preparations_tests: init_vrf: - "ip vrf {{vrf_1}}" - "ip vrf {{vrf_2}}" + - "ip vrf {{vrf_5}}" + - "ip vrf {{vrf_6}}" init_bgp: - bgp_as: "{{bgp_as_1}}" router_id: 111.2.2.41 @@ -29,13 +43,20 @@ preparations_tests: router_id: 111.2.2.41 log_neighbor_changes: False vrf_name: VrfReg1 - - bgp_as: "{{bgp_as_2}}" + - bgp_as: "{{bgp_as_1}}" router_id: 111.2.2.52 log_neighbor_changes: True vrf_name: VrfReg2 + - bgp_as: "{{bgp_as_5}}" + router_id: 111.2.2.55 + vrf_name: VrfReg5 + - bgp_as: "{{bgp_as_6}}" + router_id: 111.2.2.56 + vrf_name: VrfReg6 + as_notation: "asdot" -tests: "{{ merged_tests + deleted_tests }}" +tests: "{{ merged_tests + deleted_tests + replaced_tasks + overridden_tasks + deleted_all }}" action_tests: - name: test_case_action_01 @@ -279,10 +300,21 @@ deleted_tests: - bgp_as: "{{bgp_as_1}}" vrf_name: "{{vrf_1}}" peer_group: - - name: SPINE - name: SPINE1 - + - name: SPINE3 - name: test_case_del_08 + description: Delete neighbors/peer-group with no properties + state: deleted + input: + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_1}}" + neighbors: + - neighbor: "192.168.2.2" + peer_group: + - name: SPINE4 + +deleted_all: + - name: test_case_del_09 description: BGP NEIGHBORS remote-as properties state: deleted input: [] @@ -360,6 +392,7 @@ merged_tests: - bgp_as: "{{bgp_as_1}}" vrf_name: "{{vrf_1}}" peer_group: + - name: SPINE4 - name: SPINE remote_as: peer_type: internal @@ -410,6 +443,48 @@ merged_tests: connect_retry: 10 capability: dynamic: true + - bgp_as: "{{bgp_as_5}}" + vrf_name: "{{vrf_5}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_5_dot}}" + local_as: + as: "{{local_as_5_dot}}" + neighbors: + - neighbor: 192.168.5.5 + remote_as: + peer_as: "{{peer_as_5_dot}}" + local_as: + as: "{{local_as_5_dot}}" + peer_group: SPINE + - neighbor: 192.168.5.6 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_5_dot}}" + local_as: + as: "{{local_as_5_dot}}" + - bgp_as: "{{bgp_as_6}}" + vrf_name: "{{vrf_6}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_6}}" + local_as: + as: "{{local_as_6}}" + neighbors: + - neighbor: 192.168.6.6 + remote_as: + peer_as: "{{peer_as_6}}" + local_as: + as: "{{local_as_6}}" + peer_group: SPINE + - neighbor: 192.168.6.7 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_6}}" + local_as: + as: "{{local_as_6}}" - name: test_case_02 description: Update BGP NEIGHBORS properties state: merged @@ -418,7 +493,7 @@ merged_tests: peer_group: - name: SPINE bfd: - enabled: false + enabled: true check_failure: false profile: "profile 2" - name: SPINE1 @@ -455,7 +530,6 @@ merged_tests: bfd: enabled: false check_failure: false - profile: "profile 3" capability: dynamic: false extended_nexthop: false @@ -478,7 +552,6 @@ merged_tests: bfd: enabled: false check_failure: false - profile: "profile 2" - name: SPINE1 remote_as: peer_type: internal @@ -512,7 +585,6 @@ merged_tests: bfd: enabled: false check_failure: false - profile: "profile 4" capability: dynamic: false extended_nexthop: false @@ -539,6 +611,48 @@ merged_tests: connect_retry: 10 capability: dynamic: true + - bgp_as: "{{bgp_as_5}}" + vrf_name: "{{vrf_5}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_5}}" + neighbors: + - neighbor: 192.168.5.5 + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_5}}" + peer_group: SPINE + - neighbor: 192.168.5.6 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_5}}" + - bgp_as: "{{bgp_as_6}}" + vrf_name: "{{vrf_6}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_6_dot}}" + neighbors: + - neighbor: 192.168.6.6 + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_6_dot}}" + peer_group: SPINE + - neighbor: 192.168.6.7 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_6_dot}}" - name: test_case_03 description: BGP NEIGHBORS ipv6 properties state: merged @@ -638,6 +752,48 @@ merged_tests: - neighbor: 67.1.1.1 remote_as: peer_type: external + - bgp_as: "{{bgp_as_5}}" + vrf_name: "{{vrf_5}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_5_dot}}" + neighbors: + - neighbor: 192.168.5.5 + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_5_dot}}" + peer_group: SPINE + - neighbor: 192.168.5.6 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_6_dot}}" + local_as: + as: "{{local_as_5_dot}}" + - bgp_as: "{{bgp_as_6}}" + vrf_name: "{{vrf_6}}" + peer_group: + - name: SPINE + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_6}}" + neighbors: + - neighbor: 192.168.6.6 + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_6}}" + peer_group: SPINE + - neighbor: 192.168.6.7 + peer_group: SPINE + remote_as: + peer_as: "{{peer_as_5}}" + local_as: + as: "{{local_as_6}}" - name: test_case_05 description: BGP NEIGHBORS remote-as properties state: merged @@ -864,3 +1020,148 @@ merged_tests: safi: evpn prefix_list_in: p2 prefix_list_out: p1 + + + +replaced_tasks: + - name: test_case_replace_01 + description: Replace BGP NEIGHBORS properties + state: replaced + input: + - bgp_as: "{{bgp_as_1}}" + peer_group: + - name: SPINE3 + remote_as: + peer_as: 15 + bfd: + enabled: true + profile: "profile 2" + advertisement_interval: 10 + neighbors: + - neighbor: "{{ interface3 }}" + remote_as: + peer_as: 15 + peer_group: SPINE3 + advertisement_interval: 10 + bfd: + enabled: true + check_failure: true + profile: "profile 2" + - neighbor: "{{ interface2 }}" + remote_as: + peer_as: 15 + timers: + keepalive: 20 + holdtime: 15 + connect_retry: 60 + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_1}}" + peer_group: + - name: SPINE + capability: + dynamic: true + extended_nexthop: true + address_family: + afis: + - afi: ipv6 + safi: unicast + allowas_in: + origin: true + - name: test_case_replace_02 + description: Replace BGP neighbor + state: replaced + input: + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_1}}" + peer_group: + - name: SPINE + capability: + dynamic: true + extended_nexthop: true + address_family: + afis: + - afi: ipv6 + safi: unicast + allowas_in: + origin: true + - bgp_as: "{{bgp_as_1}}" + peer_group: + - name: SPINE3 + remote_as: + peer_as: 15 + bfd: + enabled: true + profile: "profile 2" + advertisement_interval: 10 + neighbors: + - neighbor: "{{ interface3 }}" + remote_as: + peer_as: 15 + peer_group: SPINE3 + advertisement_interval: 10 + bfd: + enabled: true + check_failure: true + profile: "profile 2" + - neighbor: "{{ interface2 }}" + peer_group: SPINE3 + advertisement_interval: 10 + - neighbor: "192.168.1.5" + advertisement_interval: 21 + capability: + dynamic: true + remote_as: + peer_as: 112 + +overridden_tasks: + - name: test_case_over_01 + description: Override BGP Neighbors properties + state: overridden + input: + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_2}}" + peer_group: + - name: SPINE3 + address_family: + afis: + - afi: ipv4 + safi: unicast + ip_afi: + default_policy_name: rmap_reg2 + send_default_route: false + prefix_list_in: p2 + prefix_list_out: p1 + neighbors: + - neighbor: "{{ interface1 }}" + remote_as: + peer_as: 1234 + - neighbor: 11::11 + remote_as: + peer_type: external + bfd: + enabled: false + timers: + keepalive: 41 + holdtime: 51 + connect_retry: 61 + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_1}}" + peer_group: + - name: SPINE + - name: test_case_over_02 + description: Override BGP Neighbors properties + state: overridden + input: + - bgp_as: "{{bgp_as_1}}" + vrf_name: "{{vrf_2}}" + peer_group: + - name: SPINE3 + address_family: + afis: + - afi: ipv4 + safi: unicast + ip_afi: + default_policy_name: rmap_reg2 + send_default_route: false + prefix_list_in: p2 + prefix_list_out: p1 diff --git a/tests/regression/roles/sonic_l3_interfaces/defaults/main.yml b/tests/regression/roles/sonic_l3_interfaces/defaults/main.yml index 5c5d5a74d..cf4fb5c4a 100644 --- a/tests/regression/roles/sonic_l3_interfaces/defaults/main.yml +++ b/tests/regression/roles/sonic_l3_interfaces/defaults/main.yml @@ -31,8 +31,12 @@ tests: - name: vlan 100 ipv6: enabled: true + dad: ENABLE + autoconf: True addresses: - address: 150::1/32 + - address: 160::/64 + eui64: True - name: po 100 ipv4: addresses: @@ -53,6 +57,7 @@ tests: secondary: True ipv6: enabled: false + autoconf: True addresses: - address: 101::1/128 - name: vlan 100 @@ -62,6 +67,8 @@ tests: - name: po 100 ipv6: enabled: true + dad: DISABLE_IPV6_ON_FAILURE + autoconf: True addresses: - address: 180::1/16 - name: vlan 102 @@ -107,8 +114,11 @@ tests: - name: "{{ interface2 }}" ipv6: enabled: true + dad: DISABLE_IPV6_ON_FAILURE addresses: - address: 90::1/16 + - address: 100::/64 + eui64: True - name: test_case_05 description: Update interface parameters state: merged @@ -157,12 +167,17 @@ tests: - name: "{{ interface2 }}" ipv6: enabled: false + dad: DISABLE_IPV6_ON_FAILURE addresses: - address: 91::1/16 + - address: 100::/64 + eui64: True - name: Loopback100 ipv4: addresses: - address: 103.1.1.1/32 + ipv6: + autoconf: True - name: vlan 102 ipv4: anycast_addresses: @@ -212,9 +227,12 @@ tests: - address: 105.1.1.1/16 ipv6: enabled: true + dad: ENABLE addresses: - address: 1051::1/16 - address: 1052::1/16 + - address: 1060::/64 + eui64: True - name: lo101 ipv4: addresses: @@ -277,7 +295,30 @@ tests: ipv4: addresses: - address: 105.2.2.2/16 + ipv6: + enabled: true + dad: DISABLE_IPV6_ON_FAILURE + addresses: + - address: 1050::/64 + eui64: True - name: test_case_14 + description: Replace interface parameters + state: replaced + input: + - name: '{{ interface2 }}' + ipv6: + enabled: true + dad: ENABLE + addresses: + - address: 251::1/64 + - address: 252::1/64 + - name: '{{ interface3 }}' + ipv4: + addresses: + - address: 222.1.1.1/24 + - address: 223.1.1.1/24 + secondary: true + - name: test_case_15 description: Override interface parameters state: overridden input: @@ -300,9 +341,7 @@ tests: anycast_addresses: - 20.21.22.23/16 - 85.1.1.12/16 - ipv6: - enabled: false - - name: test_case_15 + - name: test_case_16 description: Override2 interface parameters state: overridden input: @@ -326,9 +365,34 @@ tests: anycast_addresses: - 20.21.22.23/16 - 85.1.1.12/16 + - name: test_case_17 + description: Override interface parameters + state: overridden + input: + - name: vlan 501 ipv6: - enabled: false - - name: test_case_16 + enabled: true + addresses: + - address: 1053::1/64 + - address: 1054::1/64 + - address: 1055::1/64 + - address: 1056::2/64 + - address: 1057::2/64 + - address: 1058::2/64 + - name: Loopback101 + ipv4: + addresses: + - address: 152.1.1.1/32 + - address: 153.1.1.1/32 + secondary: true + - address: 154.1.1.1/32 + secondary: true + - name: vlan 100 + ipv4: + anycast_addresses: + - 20.21.22.23/16 + - 85.1.1.12/16 + - name: test_case_18 description: Delete all interfaces config state: deleted input: diff --git a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml index c320445e6..ff0aa41b4 100644 --- a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml +++ b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml @@ -30,6 +30,9 @@ tests: members: interfaces: - member: "{{ interface1 }}" + ethernet_segment: + esi_type: ethernet_segment_id + esi: 00:00:00:00:44:38:39:ff:00:01 - name: PortChannel41 mode: lacp members: @@ -47,6 +50,9 @@ tests: interfaces: - member: "{{ interface1 }}" - member: "{{ interface2 }}" + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 - name: PortChannel41 mode: lacp members: @@ -62,6 +68,9 @@ tests: members: interfaces: - member: "{{ interface1 }}" + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 - name: PortChannel41 members: interfaces: @@ -74,6 +83,9 @@ tests: members: interfaces: - member: "{{ interface1 }}" + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 - name: po41 members: interfaces: @@ -86,10 +98,15 @@ tests: members: interfaces: - member: "{{ interface5 }}" + ethernet_segment: + esi_type: auto_system_mac - name: po41 members: interfaces: - member: "{{ interface6 }}" + ethernet_segment: + esi_type: auto_lacp + df_preference: 2233 - name: test_case_07 description: Override portchannel configuration state: overridden @@ -98,6 +115,9 @@ tests: members: interfaces: - member: "{{ interface1 }}" + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 - name: po41 members: interfaces: @@ -106,6 +126,13 @@ tests: description: Override all portchannel configuration state: overridden input: + - name: portchannel 40 + members: + interfaces: + - member: "{{ interface1 }}" + ethernet_segment: + esi_type: auto_system_mac + df_preference: 3333 - name: portchannel 42 members: interfaces: diff --git a/tests/regression/roles/sonic_ldap/defaults/main.yml b/tests/regression/roles/sonic_ldap/defaults/main.yml new file mode 100644 index 000000000..f61bdf491 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/defaults/main.yml @@ -0,0 +1,326 @@ +--- +ansible_connection: httpapi +module_name: ldap + +vrf1: "VrfReg1" +vrf2: "VrfReg2" + +preparations_tests: + vrfs: + - name: '{{ vrf1 }}' + - name: '{{ vrf2 }}' + +tests: + - name: test_case_01 + description: Add LDAP global and nss configuration + state: merged + input: + - name: "global" + port: 389 + version: 3 + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + priority: 10 + port: 1550 + ssl: start_tls + binddn: "CN=example.com" + map: + default_attribute: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + objectclass: + - from: "attr1" + to: "attr3" + - name: "nss" + nss_base_netgroup: "group1" + idle_timelimit: 25 + timelimit: 15 + scope: "sub" + nss_base_sudoers: "sudo1" + - name: test_case_02 + description: Add LDAP pam and sudo configuration + state: merged + input: + - name: "pam" + base: "admin" + binddn: "CN=example.com" + pam_login_attribute: "loginattrstring" + retry: 3 + scope: one + - name: "global" + nss_base_passwd: password + pam_login_attribute: "loginattrstring" + nss_skipmembers: false + vrf: '{{ vrf1 }}' + - name: "sudo" + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "base_name" + sudoers_search_filter: "filter1" + timelimit: 10 + version: 3 + - name: test_case_03 + description: Change global and sudo LDAP configurations + state: merged + input: + - name: "global" + nss_base_passwd: password2 + pam_login_attribute: "loginattrstring2" + nss_skipmembers: false + idle_timelimit: 20 + vrf: '{{ vrf2 }}' + ssl: "off" + servers: + - address: 89.0.142.85 + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - secadmin + - admin + - name: sudo + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "admin" + binddn: "CN=example.com" + bind_timelimit: 5 + sudoers_search_filter: "filter1" + - name: test_case_04 + description: Change nss and pam LDAP configurations and add global sonic role + state: merged + input: + - name: nss + binddn: "CN=example.com" + idle_timelimit: 20 + nss_base_netgroup: "group2" + nss_base_sudoers: "sudo1" + scope: sub + - name: pam + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "admin" + binddn: "CN=example.com" + retry: 3 + scope: one + - name: global + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - secadmin + - operator + - name: test_case_del_01 + description: Delete global and pam LDAP configurations + state: deleted + input: + - name: global + vrf: '{{ vrf2 }}' + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - secadmin + nss_base_passwd: password2 + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + - name: pam + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: admin + binddn: "CN=example.com" + retry: 3 + scope: one + - name: test_case_del_02 + description: Delete entire nss and sudo configurations + state: deleted + input: + - name: nss + - name: sudo + - name: test_case_del_03 + description: Delete entire global LDAP configurations + state: deleted + input: + - name: global + - name: test_case_05 + description: Adding LDAP configurations + state: merged + input: + - name: global + servers: + - address: 89.0.142.86 + ssl: 'off' + - address: 244.178.44.111 + ssl: 'off' + priority: 10 + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + map: + override_attribute: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + attribute: + - from: "attr1" + to: "attr3" + - name: nss + nss_base_netgroup: "group2" + binddn: "CN=example.com" + idle_timelimit: 20 + timelimit: 15 + scope: "sub" + nss_base_sudoers: "sudo1" + - name: test_case_replace_01 + description: Replace global and sudo configurations + state: replaced + input: + - name: global + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + port: 390 + version: 2 + servers: + - address: 89.0.142.85 + priority: 10 + port: 1550 + ssl: 'start_tls' + binddn: "CN=example.com" + source_interface: "{{ interface5 }}" + map: + default_attribute: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + objectclass: + - from: "attr1" + to: "attr3" + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - secadmin + - admin + - remote_group: "user2" + sonic_roles: + - secadmin + - remote_group: "user3" + sonic_roles: + - secadmin + - admin + - operator + pam_login_attribute: "loginattrstring2" + nss_skipmembers: false + ssl: "off" + - name: sudo + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "base_name" + sudoers_base: "auto" + sudoers_search_filter: "filter1" + timelimit: 10 + version: 3 + binddn: "CN=example.com" + bind_timelimit: 5 + - name: test_case_replace_02 + description: Replace LDAP configurations for sonic roles and server + state: replaced + input: + - name: global + servers: + - address: 89.0.142.85 + priority: 10 + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - secadmin + - admin + - remote_group: "user2" + sonic_roles: + - secadmin + - admin + - remote_group: "user3" + sonic_roles: + - secadmin + - remote_group: "user4" + sonic_roles: + - operator + - name: test_case_overridden_01 + description: Override LDAP configurations + state: overridden + input: + - name: global + servers: + - address: 89.0.142.85 + ssl: 'off' + - address: 244.178.44.111 + ssl: 'off' + priority: 10 + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + map: + objectclass: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + - name: test_case_overridden_02 + description: Override LDAP configurations with LDAP configurations + state: overridden + input: + - name: global + servers: + - address: 89.0.142.85 + ssl: 'off' + - address: 244.178.44.111 + ssl: 'off' + priority: 10 + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + map: + objectclass: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + - name: nss + binddn: "CN=example.com" + idle_timelimit: 20 + nss_base_netgroup: "group2" + nss_base_sudoers: "sudo1" + scope: sub + - name: pam + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "admin" + binddn: "CN=example.com" + retry: 3 + scope: one + - name: sudo + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "base_name" + sudoers_base: "auto" + sudoers_search_filter: "filter1" + timelimit: 10 + version: 2 + - name: test_case_delete_all + description: Delete all LDAP configurations + state: deleted + input: + [] diff --git a/tests/regression/roles/sonic_ldap/meta/main.yml b/tests/regression/roles/sonic_ldap/meta/main.yml new file mode 100644 index 000000000..d0ceaf6f5 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_ldap/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ldap/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..e6fcb17d0 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/tasks/cleanup_tests.yaml @@ -0,0 +1,12 @@ +--- +- name: Delete LDAP configurations + dellemc.enterprise_sonic.sonic_ldap: + config: [] + state: deleted + ignore_errors: yes + +- name: Delete test VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: deleted + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ldap/tasks/main.yml b/tests/regression/roles/sonic_ldap/tasks/main.yml new file mode 100644 index 000000000..41b04ed46 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- ansible.builtin.debug: + msg: "sonic_ldap Test started ..." + +- name: "Preparations for {{ module_name }}" + ansible.builtin.include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started" + ansible.builtin.include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: "Cleanup of {{ module_name }}" + ansible.builtin.include_tasks: cleanup_tests.yaml + +- name: Display all variables/facts known for a host + ansible.builtin.debug: + var: hostvars[inventory_hostname].ansible_facts.test_reports diff --git a/tests/regression/roles/sonic_ldap/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ldap/tasks/preparation_tests.yaml new file mode 100644 index 000000000..45f40e554 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/tasks/preparation_tests.yaml @@ -0,0 +1,12 @@ +--- +- name: Delete old LDAP configurations + dellemc.enterprise_sonic.sonic_ldap: + config: [] + state: deleted + ignore_errors: yes + +- name: Create VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: merged + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ldap/tasks/tasks_template.yaml b/tests/regression/roles/sonic_ldap/tasks/tasks_template.yaml new file mode 100644 index 000000000..684f353e4 --- /dev/null +++ b/tests/regression/roles/sonic_ldap/tasks/tasks_template.yaml @@ -0,0 +1,22 @@ +--- +- name: "{{ item.name }} , {{ item.description }}" + dellemc.enterprise_sonic.sonic_ldap: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + dellemc.enterprise_sonic.sonic_ldap: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/roles/sonic_mgmt_servers/defaults/main.yml b/tests/regression/roles/sonic_mgmt_servers/defaults/main.yml new file mode 100644 index 000000000..53373d4e8 --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/defaults/main.yml @@ -0,0 +1,139 @@ +--- +ansible_connection: httpapi +module_name: mgmt_servers + +tests: + - name: test_case_01 + description: Initial configuration of REST and telemetry servers + state: merged + input: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + + - name: test_case_02 + description: Update mgmt servers configuration + state: merged + input: + rest: + api_timeout: 240 + client_auth: none + log_level: 12 + read_timeout: 65 + req_limit: 150 + security_profile: profile2 + telemetry: + api_timeout: 90 + client_auth: jwt,password + jwt_refresh: 160 + jwt_valid: 600 + log_level: 20 + port: 5678 + security_profile: profile1 + + - name: test_case_03 + description: Replace mgmt servers configuration + state: replaced + input: + rest: + log_level: 24 + read_timeout: 130 + req_limit: 500 + telemetry: + jwt_valid: 800 + log_level: 25 + port: 9876 + security_profile: profile1 + + - name: test_case_04 + description: Override mgmt servers configuration + state: overridden + input: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + + - name: test_case_05 + description: Delete individual mgmt servers attributes + state: deleted + input: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + + - name: test_case_06 + description: Configure REST and telemetry servers for delete all + state: merged + input: + rest: + api_timeout: 120 + client_auth: password + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + + - name: test_case_07 + description: Delete all mgmt servers configuration + state: deleted + input: {} diff --git a/tests/regression/roles/sonic_mgmt_servers/meta/main.yaml b/tests/regression/roles/sonic_mgmt_servers/meta/main.yaml new file mode 100644 index 000000000..0b356217e --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/meta/main.yaml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_mgmt_servers/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_mgmt_servers/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..a1cfb27c4 --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/tasks/cleanup_tests.yaml @@ -0,0 +1,11 @@ +- name: Delete mgmt VRF + sonic_vrfs: + config: [] + state: deleted + ignore_errors: yes + +- name: Delete security profiles + sonic_pki: + config: {} + state: deleted + ignore_errors: yes diff --git a/tests/regression/roles/sonic_mgmt_servers/tasks/main.yml b/tests/regression/roles/sonic_mgmt_servers/tasks/main.yml new file mode 100644 index 000000000..e1ddd97b7 --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/tasks/main.yml @@ -0,0 +1,14 @@ +- debug: msg="sonic_mgmt_servers test started ..." + +- set_fact: + base_cfg_path: "{{ playbook_dir + '/roles/' + role_name + '/' + 'templates/' }}" + +- name: Preparation tests + include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started ..." + include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: Cleanup tests + include_tasks: cleanup_tests.yaml diff --git a/tests/regression/roles/sonic_mgmt_servers/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_mgmt_servers/tasks/preparation_tests.yaml new file mode 100644 index 000000000..e6d1d6e78 --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/tasks/preparation_tests.yaml @@ -0,0 +1,17 @@ +- name: Delete mgmt servers configuration + sonic_mgmt_servers: + config: {} + state: deleted + ignore_errors: yes + +- name: Create mgmt VRF + sonic_vrfs: + config: + - name: mgmt + +- name: Create security profiles + sonic_pki: + config: + security_profiles: + - profile_name: profile1 + - profile_name: profile2 diff --git a/tests/regression/roles/sonic_mgmt_servers/tasks/tasks_template.yaml b/tests/regression/roles/sonic_mgmt_servers/tasks/tasks_template.yaml new file mode 100644 index 000000000..c7c984a67 --- /dev/null +++ b/tests/regression/roles/sonic_mgmt_servers/tasks/tasks_template.yaml @@ -0,0 +1,21 @@ +- name: "{{ item.name }} , {{ item.description }}" + sonic_mgmt_servers: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + sonic_mgmt_servers: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/roles/sonic_ntp/defaults/main.yml b/tests/regression/roles/sonic_ntp/defaults/main.yml index d5538e51c..0e16b96cf 100644 --- a/tests/regression/roles/sonic_ntp/defaults/main.yml +++ b/tests/regression/roles/sonic_ntp/defaults/main.yml @@ -8,6 +8,7 @@ vlan1: Vlan 100 lo1: Loopback 100 mgmt_vrf: mgmt +data_vrf: Vrf_1 ntp_ip_server_1: 10.11.0.1 ntp_ip_server_2: 10.11.0.2 @@ -26,10 +27,12 @@ preparations_tests: - "interface {{ po2 }}" - "interface {{ vlan1 }}" - "interface {{ lo1 }}" - delete_mgmt_vrf: + delete_vrfs: - "no ip vrf mgmt" - create_mgmt_vrf: + - "no ip vrf Vrf_1" + create_vrfs: - "ip vrf mgmt" + - "ip vrf Vrf_1" tests: - name: test_case_01 @@ -209,6 +212,7 @@ tests: key_id: 2 minpoll: 6 maxpoll: 9 + vrf: "{{ data_vrf }}" - name: test_case_19 description: Delete NTP trusted keys @@ -224,6 +228,7 @@ tests: input: servers: - address: "{{ ntp_ip_server_1 }}" + vrf: "{{ data_vrf }}" - name: test_case_21 description: Delete NTP authentication keys @@ -260,6 +265,7 @@ tests: minpoll: 5 maxpoll: 8 prefer: true + vrf: "{{ data_vrf }}" - name: test_case_24 description: Replace more NTP configuration @@ -277,6 +283,7 @@ tests: minpoll: 5 maxpoll: 8 prefer: true + vrf: "{{ data_vrf }}" - name: test_case_25 description: Delete all NTP configurations diff --git a/tests/regression/roles/sonic_ntp/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ntp/tasks/cleanup_tests.yaml index bb31455eb..f7014029c 100644 --- a/tests/regression/roles/sonic_ntp/tasks/cleanup_tests.yaml +++ b/tests/regression/roles/sonic_ntp/tasks/cleanup_tests.yaml @@ -6,10 +6,10 @@ register: output ignore_errors: yes -- name: Delete MGMT VRF +- name: Delete VRF vars: ansible_connection: network_cli sonic_config: - commands: "{{ preparations_tests.delete_mgmt_vrf }}" + commands: "{{ preparations_tests.delete_vrfs }}" register: output ignore_errors: yes diff --git a/tests/regression/roles/sonic_ntp/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ntp/tasks/preparation_tests.yaml index f74a2019a..bdda58a59 100644 --- a/tests/regression/roles/sonic_ntp/tasks/preparation_tests.yaml +++ b/tests/regression/roles/sonic_ntp/tasks/preparation_tests.yaml @@ -12,10 +12,10 @@ register: output ignore_errors: yes -- name: Create MGMT VRF +- name: Create VRF vars: ansible_connection: network_cli sonic_config: - commands: "{{ preparations_tests.create_mgmt_vrf }}" + commands: "{{ preparations_tests.create_vrfs }}" register: output ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospf_area/defaults/main.yml b/tests/regression/roles/sonic_ospf_area/defaults/main.yml new file mode 100644 index 000000000..12dc1a427 --- /dev/null +++ b/tests/regression/roles/sonic_ospf_area/defaults/main.yml @@ -0,0 +1,322 @@ +--- +ansible_connection: httpapi +module_name: sonic_ospf_area + +vrf_1: Vrf1 + +# keys from switch that runs nightly regression +key1: U2FsdGVkX1/+VYvX04WOQhu2FJNG/GW51El4z8NgdwE= +key2: U2FsdGVkX1/wpAfKEX0dF35jruXFQ4q4z/92Nh80ORQ= +key7: U2FsdGVkX1//KvOZ02oT2EQHkPsPMq7S7MhbgokoZzs= +key3: U2FsdGVkX18B/AyQSkMUpy3DvuufhfKMxx77VKMjTGs= +key8: U2FsdGVkX18gVkebG/p0IbbrpVZUtsoq+R36CUYk920= + +preparations_tests: + cleanup: + - "no router ospf vrf {{vrf_1}}" + - "no ip vrf {{vrf_1}}" + - "no ip prefix-list pf1" + - "no ip prefix-list pf2" + + setup: + - "ip vrf {{vrf_1}}" + - "router ospf vrf {{vrf_1}}" + - "ip prefix-list pf1 seq 10 permit 1.2.3.4/24 ge 26 le 30" + - "ip prefix-list pf2 seq 10 permit 1.2.3.4/24 ge 26 le 30" + +tests: +- name: test_case_01_creates + description: create ospf area + state: merged + input: + - area_id: 1 + vrf_name: "{{vrf_1}}" + - area_id: 2 + vrf_name: "{{vrf_1}}" + authentication_type: message_digest + default_cost: 3 + stub: + enabled: True + no_summary: True + shortcut: default + - area_id: 3 + vrf_name: "{{vrf_1}}" + filter_list_in: pf1 + filter_list_out: pf2 + ranges: + - prefix: 1.1.1.1/24 + advertise: True + cost: 4 + - prefix: 1.1.1.2/24 + advertise: False + substitute: 2.2.2.2/24 + - prefix: 1.1.1.3/24 + advertise: True + cost: 10 + substitute: 3.3.3.3/24 + - area_id: 4 + vrf_name: "{{vrf_1}}" + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 5 + vrf_name: "{{vrf_1}}" + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + hello_interval: 30 + retransmit_interval: 40 + transmit_delay: 50 + authentication: + auth_type: text + key: "{{ key1 }}" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "{{ key2 }}" + key_encrypted: True + - key_id: 3 + key: "{{ key7 }}" + key_encrypted: True + - router_id: 34.7.35.3 + hello_interval: 20 + authentication: + auth_type: message_digest + key: "{{ key3 }}" + key_encrypted: True + +- name: test_case_02_delete_partial + description: delete ospf area + state: deleted + input: + - area_id: 1 + vrf_name: "{{vrf_1}}" + - area_id: 2 + vrf_name: "{{vrf_1}}" + authentication_type: message_digest + stub: + no_summary: True + - area_id: 3 + vrf_name: "{{vrf_1}}" + filter_list_in: pf1 + ranges: + - prefix: 1.1.1.1/24 + - prefix: 1.1.1.2/24 + substitute: 2.2.2.2/24 + - prefix: 1.1.1.3/24 + cost: 10 + advertise: True + - area_id: 4 + vrf_name: "{{vrf_1}}" + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - area_id: 5 + vrf_name: "{{vrf_1}}" + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + transmit_delay: 50 + authentication: + auth_type: text + message_digest_list: + - key_id: 1 + key: "{{ key2 }}" + key_encrypted: True + - router_id: 34.7.35.3 + hello_interval: 20 + authentication: + key: "{{ key3 }}" + key_encrypted: True + +- name: test_case_03_replaced + description: replacing certain ospf areas + state: replaced + input: + - area_id: 1 + vrf_name: "{{vrf_1}}" + ranges: + - prefix: 1.1.1.1/24 + advertise: False + substitute: 1.1.1.2/24 + - prefix: 1.1.1.2/24 + substitute: 2.2.2.2/24 + cost: 34 + - prefix: 1.1.1.3/24 + cost: 10 + advertise: True + networks: + - "34.1.35.1/24" + - "34.1.35.2/24" + - "34.1.35.3/24" + filter_list_in: pf1 + filter_list_out: pf2 + - area_id: 2 + vrf_name: "{{vrf_1}}" + stub: + enabled: True + ranges: + - prefix: 1.2.2.2/24 + advertise: True + cost: 10 + - prefix: 1.2.2.3/24 + advertise: False + substitute: 1.2.2.4/24 + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + dead_interval: 15 + - area_id: 3 + vrf_name: "{{vrf_1}}" + authentication_type: text + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + dead_interval: 15 + hello_interval: 25 + retransmit_interval: 40 + transmit_delay: 50 + authentication: + auth_type: text + key: "{{ key8 }}" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "{{ key2 }}" + key_encrypted: True + - key_id: 3 + key: "{{ key7 }}" + key_encrypted: True + - router_id: 34.7.35.3 + hello_interval: 20 + authentication: + auth_type: message_digest + key: "{{ key3 }}" + key_encrypted: True + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - area_id: 4 + vrf_name: "{{vrf_1}}" + shortcut: default + - area_id: 8 + vrf_name: "{{vrf_1}}" + +- name: test_case_04_overridden + description: override ospf areas + state: overridden + input: + - area_id: 1 + vrf_name: "{{vrf_1}}" + shortcut: disable + - area_id: 2 + vrf_name: "{{vrf_1}}" + authentication_type: text + filter_list_in: pf1 + shortcut: disable + stub: + no_summary: True + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + ranges: + - prefix: 1.1.1.5/24 + advertise: True + cost: 4 + - prefix: 1.1.1.6/24 + advertise: False + substitute: 6.6.6.6/24 + - area_id: 3 + vrf_name: "{{vrf_1}}" + virtual_links: + - router_id: 34.7.35.1 + enabled: true + dead_interval: 15 + hello_interval: 25 + authentication: + auth_type: message_digest + key: "{{ key3 }}" + key_encrypted: True + - router_id: 34.7.35.2 + enabled: true + dead_interval: 10 + retransmit_interval: 40 + transmit_delay: 50 + message_digest_list: + - key_id: 1 + key: "{{ key2 }}" + key_encrypted: True + - key_id: 3 + key: "{{ key7 }}" + key_encrypted: True + ranges: + - prefix: 1.1.1.1/24 + advertise: True + cost: 4 + - prefix: 1.1.1.2/24 + advertise: False + substitute: 2.2.2.2/24 + - prefix: 1.1.1.3/24 + advertise: True + cost: 10 + substitute: 3.3.3.3/24 + - area_id: 6 + vrf_name: "{{vrf_1}}" + virtual_links: + - router_id: 34.7.35.3 + enabled: true + hello_interval: 20 + authentication: + auth_type: message_digest + key: "{{ key3 }}" + key_encrypted: True + +- name: test_case_05_clear_subsections + state: deleted + input: + - area_id: 2 + vrf_name: "{{vrf_1}}" + networks: [] + - area_id: 3 + vrf_name: "{{vrf_1}}" + ranges: [] + virtual_links: + - router_id: 34.7.35.1 + authentication: {} + - router_id: 34.7.35.2 + message_digest_list: [] + - area_id: 6 + vrf_name: "{{vrf_1}}" + virtual_links: [] + +- name: test_case_06_delete_areas + description: delete all of certain ospf areas + state: deleted + input: + - area_id: 1 + vrf_name: "{{vrf_1}}" + - area_id: 2 + vrf_name: "{{vrf_1}}" + authentication_type: text + filter_list_in: pf1 + shortcut: disable + stub: + no_summary: True + ranges: + - prefix: 1.1.1.5/24 + advertise: True + cost: 4 + - prefix: 1.1.1.6/24 + advertise: False + substitute: 6.6.6.6/24 + +- name: test_case_07_delete_everything + description: delete all ospf areas + state: deleted + input: [] \ No newline at end of file diff --git a/tests/regression/roles/sonic_ospf_area/meta/main.yaml b/tests/regression/roles/sonic_ospf_area/meta/main.yaml new file mode 100644 index 000000000..78f79f8ca --- /dev/null +++ b/tests/regression/roles/sonic_ospf_area/meta/main.yaml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } \ No newline at end of file diff --git a/tests/regression/roles/sonic_ospf_area/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ospf_area/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..5f4d15253 --- /dev/null +++ b/tests/regression/roles/sonic_ospf_area/tasks/cleanup_tests.yaml @@ -0,0 +1,7 @@ +- name: Delete testing vrf + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.cleanup }}" + register: output + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospf_area/tasks/main.yml b/tests/regression/roles/sonic_ospf_area/tasks/main.yml new file mode 100644 index 000000000..487966020 --- /dev/null +++ b/tests/regression/roles/sonic_ospf_area/tasks/main.yml @@ -0,0 +1,14 @@ +- debug: msg="sonic_ospf_area Test started ..." + +- name: Preparations tests + include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started ..." + vars: + ansible_connection: httpapi + include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: "Cleanup test {{ module_name }} started" + include_tasks: cleanup_tests.yaml + diff --git a/tests/regression/roles/sonic_ospf_area/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ospf_area/tasks/preparation_tests.yaml new file mode 100644 index 000000000..d5b67d065 --- /dev/null +++ b/tests/regression/roles/sonic_ospf_area/tasks/preparation_tests.yaml @@ -0,0 +1,13 @@ +- name: Create testing Vrf + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.setup }}" + +- name: Delete existing ospf area configurations + vars: + ansible_connection: httpapi + sonic_ospf_area: + config: [] + state: deleted + ignore_errors: yes diff --git a/tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml b/tests/regression/roles/sonic_ospf_area/tasks/tasks_template.yaml similarity index 80% rename from tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml rename to tests/regression/roles/sonic_ospf_area/tasks/tasks_template.yaml index b50cc26cd..70eb83ba2 100644 --- a/tests/regression/roles/sonic_aaa/tasks/tasks_template_del.yaml +++ b/tests/regression/roles/sonic_ospf_area/tasks/tasks_template.yaml @@ -1,18 +1,18 @@ - name: "{{ item.name}} , {{ item.description}}" - sonic_aaa: + sonic_ospf_area: + config: "{{ item.input }}" state: "{{ item.state }}" - config: register: action_task_output ignore_errors: yes - import_role: name: common tasks_from: action.facts.report.yaml - + - name: "{{ item.name}} , {{ item.description}} Idempotent" - sonic_aaa: + sonic_ospf_area: + config: "{{ item.input }}" state: "{{ item.state }}" - config: register: idempotent_task_output ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospfv2/defaults/main.yml b/tests/regression/roles/sonic_ospfv2/defaults/main.yml new file mode 100644 index 000000000..6783b75d5 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/defaults/main.yml @@ -0,0 +1,277 @@ +--- +ansible_connection: httpapi +module_name: ospfv2 + +vrf1: "VrfReg1" +vrf2: "VrfReg2" + +preparations_tests: + init_route_map: + - route-map rmap_reg1 permit 11 + - route-map rmap_reg2 permit 11 + vrfs: + - name: '{{ vrf1 }}' + - name: '{{ vrf2 }}' + +tests: + - name: test_case_01 + description: Add OSPFv2 configuration + state: merged + input: + - vrf_name: 'default' + router_id: "10.10.10.10" + distance: + external: 20 + redistribute: + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + - protocol: "default_route" + always: true + metric: 10 + graceful_restart: + grace_period: 100 + helper: + enable: true + planned_only: true + advertise_router_id: + - '1.1.1.1' + - '2.2.2.2' + - vrf_name: '{{ vrf1 }}' + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - name: test_case_02 + description: Add OSPFv2 configuration for Vrfs + state: merged + input: + - vrf_name: '{{ vrf2 }}' + write_multiplier: 20 + router_id: "20.20.20.20" + distance: + all: 30 + default_passive: false + passive_interfaces: + - interface: '{{ interface6 }}' + addresses: + - '3.3.3.3' + - interface: '{{ interface7 }}' + - abr_type: "cisco" + redistribute: + - protocol: 'default_route' + always: true + metric: 10 + metric_type: 2 + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg2" + - vrf_name: '{{ vrf1 }}' + non_passive_interfaces: + - interface: '{{ interface5 }}' + - name: test_case_del_01 + description: Delete OSPFv2 configuration + state: deleted + input: + - vrf_name: '{{ vrf2 }}' + router_id: "20.20.20.20" + passive_interfaces: + - interface: '{{ interface7 }}' + - vrf_name: '{{ vrf1 }}' + timers: + throttle_lsa_all: 300 + redistribute: + - protocol: "bgp" + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - vrf_name: 'default' + graceful_restart: + enable: true + grace_period: 100 + helper: + enable: true + planned_only: true + advertise_router_id: + - '1.1.1.1' + - '2.2.2.2' + redistribute: + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg2" + - protocol: "default_route" + always: true + - name: test_case_del_02 + description: Delete entire OSPFv2 configuration for Vrf1 and Vrf2 + state: deleted + input: + - vrf_name: '{{ vrf1 }}' + - vrf_name: '{{ vrf2 }}' + - name: test_case_03 + description: Add OSPFv2 configuration for Vrf2 and default + state: merged + input: + - vrf_name: '{{ vrf2 }}' + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + max_metric: + administrative: True + external_lsa_connected: 2127 + router_lsa_stub: 2128 + on_startup: 20 + - vrf_name: 'default' + redistribute: + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg2" + - name: test_case_replace_01 + description: Replace OSPFv2 configuration + state: replaced + input: + - vrf_name: '{{ vrf2 }}' + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - vrf_name: '{{ vrf1 }}' + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + - name: test_case_replace_02 + description: Replace OSPFv2 configuration with existing same configuration + state: replaced + input: + - vrf_name: '{{ vrf1 }}' + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + - vrf_name: 'default' + router_id: "10.10.10.10" + default_passive: true + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - name: test_case_over_01 + description: Override OSPFv2 configuration + state: overridden + input: + - vrf_name: '{{ vrf1 }}' + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + - vrf_name: 'default' + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - name: test_case_over_02 + description: Override OSPFv2 configuration with existing same configuration + state: overridden + input: + - vrf_name: 'default' + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: '{{ interface5 }}' + addresses: + - "2.2.2.2" + - name: test_case_del_all + description: Delete all OSPFv2 configuration + state: deleted + input: [] diff --git a/tests/regression/roles/sonic_ospfv2/meta/main.yml b/tests/regression/roles/sonic_ospfv2/meta/main.yml new file mode 100644 index 000000000..d0ceaf6f5 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_ospfv2/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ospfv2/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..b3c87b297 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/tasks/cleanup_tests.yaml @@ -0,0 +1,12 @@ +--- +- name: Delete OSPFv2 configurations + dellemc.enterprise_sonic.sonic_ospfv2: + config: [] + state: deleted + ignore_errors: yes + +- name: Delete test VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: deleted + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospfv2/tasks/main.yml b/tests/regression/roles/sonic_ospfv2/tasks/main.yml new file mode 100644 index 000000000..d976fb790 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- ansible.builtin.debug: + msg: "sonic_ospfv2 Test started ..." + +- name: "Preparations for {{ module_name }}" + ansible.builtin.include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started" + ansible.builtin.include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: "Cleanup of {{ module_name }}" + ansible.builtin.include_tasks: cleanup_tests.yaml diff --git a/tests/regression/roles/sonic_ospfv2/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ospfv2/tasks/preparation_tests.yaml new file mode 100644 index 000000000..ba7d8b4a7 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/tasks/preparation_tests.yaml @@ -0,0 +1,26 @@ +--- +- name: Delete old OSPFv2 configurations + dellemc.enterprise_sonic.sonic_ospfv2: + config: [] + state: deleted + ignore_errors: yes + +- name: Initialize default interfaces + vars: + ansible_connection: network_cli + dellemc.enterprise_sonic.sonic_config: + commands: "{{ default_interface_cli }}" + register: output + ignore_errors: yes + +- name: Create VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: merged + ignore_errors: yes + +- name: "initialize route maps" + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.init_route_map }}" diff --git a/tests/regression/roles/sonic_ospfv2/tasks/tasks_template.yaml b/tests/regression/roles/sonic_ospfv2/tasks/tasks_template.yaml new file mode 100644 index 000000000..d5113f71b --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2/tasks/tasks_template.yaml @@ -0,0 +1,22 @@ +--- +- name: "{{ item.name }} , {{ item.description }}" + dellemc.enterprise_sonic.sonic_ospfv2: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + dellemc.enterprise_sonic.sonic_ospfv2: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/defaults/main.yml b/tests/regression/roles/sonic_ospfv2_interfaces/defaults/main.yml new file mode 100644 index 000000000..ad379d752 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/defaults/main.yml @@ -0,0 +1,373 @@ +--- +ansible_connection: httpapi +module_name: ospfv2_interfaces + +po1: "PortChannel100" +po2: "PortChannel101" + +vlan1: "Vlan100" +vlan2: "Vlan101" + +lo1: "Loopback100" + +preparations_tests: + lag_interfaces: + - name: "{{ po1 }}" + - name: "{{ po2 }}" + vlans: + - vlan_id: 100 + - vlan_id: 101 + init_loopback: + - "interface {{ lo1 }}" + delete_port_configurations: + - name: "{{ interface7 }}" + - name: "{{ interface8 }}" + - name: "{{ interface9 }}" + init_ospf: + - router ospf + delete_ospf: + - no router ospf + +tests: + - name: test_case_01 + description: Add OSPFv2 interface configurations for Ethernet interfaces + state: merged + input: + - name: "{{ interface7 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: "{{ interface8 }}" + ospf_attributes: + - area_id: '3.3.3.3' + address: '10.19.120.2' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + network: point_to_point + - name: "{{ interface9 }}" + bfd: + enable: True + bfd_profile: 'profile1' + - name: test_case_02 + description: Add OSPFv2 interface configurations for Lag interfaces + state: merged + input: + - name: "{{ po1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 15 + retransmit_interval: 100 + transmit_delay: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - key_id: 20 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + network: broadcast + bfd: + enable: True + bfd_profile: 'profile1' + - name: "{{ po2 }}" + ospf_attributes: + - area_id: '3.3.3.3' + address: '10.19.120.2' + authentication_type: 'TEXT' + - name: test_case_03 + description: Add OSPFv2 interface configurations for Vlan and Loopback interfaces + state: merged + input: + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: "{{ vlan2 }}" + bfd: + enable: True + bfd_profile: 'profile2' + network: broadcast + - name: "{{ lo1 }}" + network: point_to_point + - name: test_case_04 + description: Add OSPFv2 interface configurations + state: merged + input: + - name: "{{ po1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 15 + - address: '10.10.120.1' + mtu_ignore: True + authentication_type: 'TEXT' + md_authentication: + - key_id: 30 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + network: point_to_point + bfd: + enable: True + bfd_profile: 'profile2' + - name: "{{ interface7 }}" + ospf_attributes: + - area_id: '4.4.4.4' + cost: 15 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: "{{ vlan2 }}" + bfd: + enable: True + bfd_profile: 'profile2' + network: broadcast + ospf_attributes: + - area_id: '1.1.1.1' + address: '10.19.120.2' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - area_id: 33686018 + address: '10.19.120.3' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - name: "{{ lo1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: point_to_point + - name: test_case_del_01 + description: Delete OSPFv2 interface configurations + state: deleted + input: + - name: "{{ lo1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: "{{ interface7 }}" + ospf_attributes: [] + bfd: + enable: True + - name: "{{ vlan1 }}" + ospf_attributes: + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: "{{ po1 }}" + ospf_attributes: + - address: '10.10.120.1' + network: broadcast + - name: test_case_del_02 + description: Delete OSPFv2 interface configurations for interfaces default address + state: deleted + input: + - name: "{{ po1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 15 + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + hello_multiplier: 2 + - name: test_case_del_03 + description: Delete entire OSPFv2 for an interface. + state: deleted + input: + - name: "{{ interface7 }}" + - name: "{{ vlan2 }}" + - name: "{{ po2 }}" + - name: test_case_05 + description: Add OSPFv2 interface configurations for interface level + state: merged + input: + - name: "{{ interface7 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + - area_id: '4.4.4.4' + address: '10.10.120.10' + cost: 15 + hello_multiplier: 2 + - address: '10.10.120.2' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: test_case_replace_01 + description: Replace existing OSPFv2 interface configurations for interface level + state: replaced + input: + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: '3.3.3.3' + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + - name: "{{ lo1 }}" + network: point_to_point + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + - name: test_case_replace_02 + description: Replace OSPFv2 interface configurations + state: replaced + input: + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: '3.3.3.3' + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + - name: "{{ interface8 }}" + network: point_to_point + - name: "{{ po1 }}" + ospf_attributes: + - area_id: 33686018 + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - name: test_case_overridden_01 + description: Override entire OSPFv2 interface configurations + state: overridden + input: + - name: "{{ po2 }}" + ospf_attributes: + - area_id: '3.3.3.3' + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: "{{ vlan1 }}" + ospf_attributes: + - area_id: '3.3.3.3' + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + - name: test_case_delete_all + description: Delete entire OSPFv2 interface configurations + state: deleted + input: [] diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/meta/main.yml b/tests/regression/roles/sonic_ospfv2_interfaces/meta/main.yml new file mode 100644 index 000000000..d0ceaf6f5 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..4187251bf --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/cleanup_tests.yaml @@ -0,0 +1,31 @@ +--- +- name: Delete OSPFv2 interfaces configurations + dellemc.enterprise_sonic.sonic_ospfv2_interfaces: + config: [] + state: deleted + ignore_errors: yes + +- name: "Delete ospf" + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.delete_ospf }}" + ignore_errors: yes + +- name: Delete test VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ preparations_tests.vlans }}" + state: deleted + ignore_errors: yes + +- name: Delete test lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ preparations_tests.lag_interfaces }}" + state: deleted + ignore_errors: yes + +- name: Delete test Loopbacks + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ preparations_tests.vlans }}" + state: deleted + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/tasks/main.yml b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/main.yml new file mode 100644 index 000000000..9faaf66c9 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- ansible.builtin.debug: + msg: "sonic_ospfv2 interfaces Test started ..." + +- name: "Preparations test, creates VLANs" + ansible.builtin.include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started" + ansible.builtin.include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: "Cleanup of {{ module_name }}" + ansible.builtin.include_tasks: cleanup_tests.yaml diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/preparation_tests.yaml new file mode 100644 index 000000000..566aa2882 --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/preparation_tests.yaml @@ -0,0 +1,32 @@ +--- +- name: Delete old OSPFv2 interfaces configurations + dellemc.enterprise_sonic.sonic_ospfv2_interfaces: + config: [] + state: deleted + ignore_errors: yes + +- name: "initialize ospf" + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.init_ospf }}" + ignore_errors: yes + +- name: "initialize init_loopback" + vars: + ansible_connection: network_cli + sonic_config: + commands: "{{ preparations_tests.init_loopback }}" + ignore_errors: yes + +- name: Create VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ preparations_tests.vlans }}" + state: merged + ignore_errors: yes + +- name: Create lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ preparations_tests.lag_interfaces }}" + state: merged + ignore_errors: yes diff --git a/tests/regression/roles/sonic_ospfv2_interfaces/tasks/tasks_template.yaml b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/tasks_template.yaml new file mode 100644 index 000000000..d71e3300d --- /dev/null +++ b/tests/regression/roles/sonic_ospfv2_interfaces/tasks/tasks_template.yaml @@ -0,0 +1,22 @@ +--- +- name: "{{ item.name }} , {{ item.description }}" + dellemc.enterprise_sonic.sonic_ospfv2_interfaces: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + dellemc.enterprise_sonic.sonic_ospfv2_interfaces: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/roles/sonic_route_maps/defaults/main.yml b/tests/regression/roles/sonic_route_maps/defaults/main.yml index e700c3f47..e67383d87 100644 --- a/tests/regression/roles/sonic_route_maps/defaults/main.yml +++ b/tests/regression/roles/sonic_route_maps/defaults/main.yml @@ -80,7 +80,7 @@ tests: tag: 7284 - name: test_case_02_merged_base_rm1_80_set - description: Add initial route map set configuration for map rm1 80 + description: Add initial route map set configuration for map rm1 80 and rm6 100 state: merged input: - map_name: rm1 @@ -114,6 +114,17 @@ tests: value: 870 origin: egp weight: 93471 + tag: 65 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.200,1000.315,7135 + extcommunity: + rt: + - "200.30:40" + soo: + - "1000.149:78" - name: test_case_03_merged_base_other_route_maps description: Add initial route map match configuration for other maps state: merged @@ -183,7 +194,7 @@ tests: peer: interface: PortChannel14 - name: test_case_05_merged_modify_rm1_80_set - description: Modify route map set configuration for map rm1 80 + description: Modify route map set configuration for map rm1 80 and rm6 100 state: merged input: - map_name: rm1 @@ -198,6 +209,16 @@ tests: prefer_global: false metric: rtt_action: add + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.188,257 + extcommunity: + rt: + - "100.30:50" + soo: + - "1000.149:78" - name: test_case_06_merged_modify_other_route_maps description: Modify route map configuration for other maps state: merged @@ -287,6 +308,7 @@ tests: rtt_action: add origin: egp weight: 93471 + tag: 83540 - map_name: rm1 action: deny sequence_num: 3047 @@ -330,6 +352,14 @@ tests: sequence_num: 480 match: source_protocol: static + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 188,257 + extcommunity: + rt: + - "200.30:40" - name: test_case_08_merged_restore_rm1_80_match description: Restore route map match configuration for map rm1 80 state: merged @@ -360,7 +390,7 @@ tests: source_vrf: Vrf1 tag: 7284 - name: test_case_09_merged_restore_rm1_80_set - description: Restore route map set configuration for map rm1 80 + description: Restore route map set configuration for map rm1 80 and rm6 100 state: merged input: - map_name: rm1 @@ -395,6 +425,15 @@ tests: rtt_action: add origin: egp weight: 93471 + tag: 83540 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 188,257 + extcommunity: + rt: + - "200.30:40" - name: test_case_10_merged_restore_other_route_maps description: Restore route map configuration for other maps state: merged @@ -495,6 +534,16 @@ tests: set: ipv6_next_hop: global_addr: 45::90 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 1.188,0.257 + extcommunity: + rt: + - "13107230:40" + soo: + - "1000.149:78" - name: test_case_12_merged_restore_rm1_80_match description: Restore route map match configuration for map rm1 80 state: merged @@ -560,6 +609,7 @@ tests: rtt_action: add origin: egp weight: 93471 + tag: 83540 - name: test_case_14_merged_restore_other_route_maps description: Restore route map configuration for other replaced maps (rm2 "set") state: merged @@ -732,6 +782,7 @@ tests: rtt_action: add origin: egp weight: 93471 + tag: 83540 - name: test_case_22_merged_restore_deleted_rm1_3047_and_rm3_route_map description: Restore deleted route maps rm1 3047 and rm3 state: merged @@ -850,6 +901,7 @@ tests: rtt_action: add origin: egp weight: 93471 + tag: 83540 - name: test_case_26_merged_restore_deleted_rm1_3047_and_rm3_route_map description: Restore deleted route maps rm1 3047 and rm3 state: merged @@ -903,6 +955,16 @@ tests: match: ip: address: ip_pfx_list2 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 1.188,0.257 + extcommunity: + rt: + - "100.30:1140" + soo: + - "1000.149:78" - name: test_case_28_merged_restore_some_deleted_route_maps description: Restore some deleted route map configuration state: merged diff --git a/tests/regression/roles/sonic_system/defaults/main.yml b/tests/regression/roles/sonic_system/defaults/main.yml index e2b4b3495..57662b0a8 100644 --- a/tests/regression/roles/sonic_system/defaults/main.yml +++ b/tests/regression/roles/sonic_system/defaults/main.yml @@ -12,6 +12,8 @@ tests: ipv4: false ipv6: false auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_HI + audit_rules: BASIC - name: test_case_02 description: Update created System properties @@ -19,6 +21,8 @@ tests: input: hostname: SONIC-new interface_naming: standard_extended + load_share_hash_algo: JENKINS_HASH_LO + audit_rules: DETAIL - name: test_case_03 description: Update System properties - associate mac address @@ -36,6 +40,8 @@ tests: anycast_address: ipv4: false auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_LO + audit_rules: BASIC - name: test_case_05 description: Delete System associated anycast mac address @@ -54,6 +60,8 @@ tests: ipv4: true mac_address: 00:09:5B:EC:EE:F2 auto_breakout: ENABLE + load_share_hash_algo: CRC_XOR + audit_rules: BASIC - name: test_case_07 description: Replace some System configuration @@ -64,6 +72,7 @@ tests: ipv6: false mac_address: 00:09:5B:EC:EE:F2 auto_breakout: DISABLE + audit_rules: DETAIL - name: test_case_08 description: Replace System configuration @@ -74,6 +83,15 @@ tests: anycast_address: ipv4: true auto_breakout: ENABLE + load_share_hash_algo: CRC_32HI + audit_rules: BASIC + + - name: test_case_09 + description: Update System properties + state: merged + input: + hostname: SONIC-reg + audit_rules: NONE test_delete_all: - name: del_all_test_case_01 diff --git a/tests/regression/roles/sonic_vlan_mapping/defaults/main.yml b/tests/regression/roles/sonic_vlan_mapping/defaults/main.yml index 1ba48941e..b5925eb43 100644 --- a/tests/regression/roles/sonic_vlan_mapping/defaults/main.yml +++ b/tests/regression/roles/sonic_vlan_mapping/defaults/main.yml @@ -23,173 +23,189 @@ tests: - name: '{{ interface2 }}' mapping: - service_vlan: 2755 - vlan_ids: - - 392 - dot1q_tunnel: false - inner_vlan: 590 + dot1q_tunnel: + vlan_ids: + - 392 + - 360-366 + priority: 3 - service_vlan: 2855 - vlan_ids: - - 393 - - 395 - dot1q_tunnel: true - priority: 6 + dot1q_tunnel: + vlan_ids: + - 396 + - 398 + priority: 6 - name: test_case_02 - description: Add vlan mapping configurations - state: merged + description: Replace dot1q_tunnel configuration + state: replaced input: - - name: '{{ interface4 }}' - mapping: - - service_vlan: 2567 - vlan_ids: - - 300 - dot1q_tunnel: true - priority: 3 - - service_vlan: 2436 - vlan_ids: - - 400-402 - - 412 - - 420 - - 422 - - 430-431 - dot1q_tunnel: true - - name: '{{ po1 }}' + - name: '{{ interface2 }}' mapping: - - service_vlan: 3000 - dot1q_tunnel: true - vlan_ids: - - 506-512 - - 561 - priority: 5 + - service_vlan: 2755 + dot1q_tunnel: + vlan_ids: + - 393 + - 360-362 + priority: 2 - name: test_case_03 - description: Update existing vlan mapping configurations + description: Add vlan mapping configurations state: merged input: - - name: '{{ interface2 }}' - mapping: - - service_vlan: 2755 - priority: 3 - - service_vlan: 2855 - vlan_ids: - - 397 - - 399 - dot1q_tunnel: true - - name: '{{ po1 }}' + - name: '{{ interface4 }}' mapping: - - service_vlan: 3000 - dot1q_tunnel: true - vlan_ids: - - 506-514 - - 501 - - 561 - priority: 1 + - service_vlan: 2567 + vlan_translation: + match_double_tags: + - inner_vlan: 600 + outer_vlan: 610 + - inner_vlan: 601 + outer_vlan: 611 + priority: 1 + - inner_vlan: 602 + outer_vlan: 612 + priority: 2 - name: test_case_04 - description: Update existing and add new vlan mapping configurations + description: Add vlan mapping configurations state: merged input: - - name: '{{ interface2 }}' - mapping: - - service_vlan: 2758 - vlan_ids: - - 2857 - - service_vlan: 2855 - priority: 2 - dot1q_tunnel: true - - name: '{{ po2 }}' + - name: '{{ interface4 }}' mapping: - - service_vlan: 3200 - dot1q_tunnel: true - vlan_ids: - - 576-584 - - 591 + - service_vlan: 2567 + vlan_translation: + match_single_tags: + - outer_vlan: 500 + - outer_vlan: 501 + priority: 1 + - outer_vlan: 502 + priority: 2 - name: test_case_05 - description: Replace vlan mapping configurations + description: Replace vlan translation configuration state: replaced input: - - name: '{{ interface2 }}' - mapping: - - service_vlan: 2768 - vlan_ids: - - 2923 - name: '{{ interface4 }}' mapping: - service_vlan: 2567 - vlan_ids: - - 310 - - service_vlan: 2436 - vlan_ids: - - 400-402 - - 422 - - 430-431 - dot1q_tunnel: true - priority: 1 - - name: '{{ po1 }}' - mapping: - - service_vlan: 3000 - dot1q_tunnel: true - vlan_ids: - - 506-512 - - 561 - priority: 7 + vlan_translation: + match_double_tags: + - inner_vlan: 600 + outer_vlan: 610 + - inner_vlan: 801 + outer_vlan: 811 + priority: 2 - name: test_case_06 - description: Delete vlan mapping configurations + description: Delete dot1q_tunnel configuration state: deleted input: - name: '{{ interface2 }}' + mapping: + - service_vlan: 2755 + dot1q_tunnel: + vlan_ids: + - 392 + - 360-362 + priority: 3 + - name: test_case_07 + description: Delete vlan translation configuration + state: deleted + input: + - name: '{{ interface4 }}' + mapping: + - service_vlan: 2567 + vlan_translation: + match_double_tags: + - inner_vlan: 600 + outer_vlan: 610 + - inner_vlan: 601 + outer_vlan: 611 + priority: 1 + - name: test_case_08 + description: Delete more vlan translation configuration + state: deleted + input: - name: '{{ interface4 }}' mapping: - service_vlan: 2567 - - service_vlan: 2436 - vlan_ids: - - 422 - - 400-405 - priority: 1 - - name: '{{ po1 }}' + vlan_translation: + match_single_tags: + - outer_vlan: 500 + - outer_vlan: 501 + priority: 1 + - name: test_case_09 + description: Override vlan translation configuration + state: overridden + input: + - name: '{{ interface4 }}' mapping: - - service_vlan: 3000 - priority: 7 - - name: '{{ po2 }}' + - service_vlan: 2567 + vlan_translation: + match_double_tags: + - inner_vlan: 700 + outer_vlan: 710 + - inner_vlan: 701 + outer_vlan: 711 + - name: test_case_10 + description: Delete vlan translation configuration + state: deleted + input: + - name: '{{ interface4 }}' mapping: - - service_vlan: 3200 - dot1q_tunnel: true - vlan_ids: - - 578-582 - - name: test_case_07 + - service_vlan: 2567 + - name: test_case_11 description: Add vlan mapping configurations state: merged input: - name: '{{ interface2 }}' mapping: - service_vlan: 2755 - vlan_ids: - - 392 - dot1q_tunnel: false - inner_vlan: 590 + dot1q_tunnel: + vlan_ids: + - 392-396 + priority: 5 - service_vlan: 2855 - vlan_ids: - - 393 - - 395 - dot1q_tunnel: true - priority: 6 - - name: test_case_08 - description: Override vlan mapping configurations - state: overridden - input: + dot1q_tunnel: + vlan_ids: + - 596 + - 598 + priority: 2 + - name: test_case_12 + description: Delete some vlan mapping configurations + state: deleted + input: - name: '{{ interface2 }}' mapping: - - service_vlan: 2754 - vlan_ids: - - 392 - dot1q_tunnel: false - inner_vlan: 590 - - name: '{{ interface6 }}' + - service_vlan: 2755 + dot1q_tunnel: + - name: test_case_13 + description: Add vlan mapping configurations + state: merged + input: + - name: '{{ interface2 }}' mapping: - - service_vlan: 2700 - vlan_ids: - - 132-145 - - 120 - dot1q_tunnel: true - priority: 3 - - name: test_case_09 + - service_vlan: 2567 + vlan_translation: + match_double_tags: + - inner_vlan: 300 + outer_vlan: 310 + - inner_vlan: 301 + outer_vlan: 311 + priority: 3 + - inner_vlan: 302 + outer_vlan: 312 + priority: 3 + - name: test_case_14 + description: Add vlan mapping configurations + state: merged + input: + - name: '{{ interface2 }}' + mapping: + - service_vlan: 2567 + vlan_translation: + match_single_tags: + - outer_vlan: 900 + - outer_vlan: 901 + priority: 3 + - outer_vlan: 902 + priority: 2 + - name: test_case_15 description: Delete all vlan mapping configurations state: deleted input: [] diff --git a/tests/regression/roles/sonic_vrrp/defaults/main.yml b/tests/regression/roles/sonic_vrrp/defaults/main.yml new file mode 100644 index 000000000..70ab584ba --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/defaults/main.yml @@ -0,0 +1,325 @@ +--- +ansible_connection: httpapi +module_name: vrrp + +vrf1: "VrfReg1" +vrf2: "VrfReg2" + +po1: "PortChannel100" +po2: "PortChannel101" + +vlan1: "Vlan100" +vlan2: "Vlan101" + +preparations_tests: + vrfs: + - name: '{{ vrf1 }}' + - name: '{{ vrf2 }}' + lag_interfaces: + - name: '{{ po1 }}' + - name: '{{ po2 }}' + vlans: + - vlan_id: 100 + - vlan_id: 101 + l3_interfaces: + - name: '{{ interface1 }}' + ipv4: + addresses: + - address: 80.1.1.1/24 + ipv6: + addresses: + - address: 80::1/64 + - name: '{{ interface2 }}' + ipv4: + addresses: + - address: 90.1.1.1/24 + ipv6: + addresses: + - address: 90::1/64 + - name: '{{ vlan1 }}' + ipv4: + addresses: + - address: 81.1.1.1/24 + ipv6: + addresses: + - address: 81::1/64 + - name: '{{ vlan2 }}' + ipv4: + addresses: + - address: 91.1.1.1/24 + ipv6: + addresses: + - address: 91::1/64 + - name: '{{ po1 }}' + ipv4: + addresses: + - address: 101.1.1.1/24 + ipv6: + addresses: + - address: 101::1/64 + - name: '{{ po2 }}' + ipv4: + addresses: + - address: 102.1.1.1/24 + ipv6: + addresses: + - address: 102::1/64 + +tests: + - name: test_case_01 + description: Add VRRP and VRRP6 configuration + state: merged + input: + - name: '{{ interface1 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 80.1.1.3 + - address: 80.1.1.4 + preempt: True + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 80::3 + - address: 80::4 + advertisement_interval: 4 + priority: 10 + - name: '{{ interface2 }}' + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 90.1.1.3 + priority: 20 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 90.1.1.4 + preempt: True + priority: 20 + - name: test_case_02 + description: Modify VRRP and VRRP6 configuration + state: merged + input: + - name: '{{ interface1 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 80.1.1.5 + preempt: False + use_v2_checksum: True + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 80::4 + advertisement_interval: 4 + priority: 10 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 80.1.1.6 + track_interface: + - interface: '{{ interface5 }}' + priority_increment: 10 + - name: '{{ interface2 }}' + group: + - virtual_router_id: 5 + afi: ipv4 + priority: 30 + version: 3 + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 90::10 + priority: 20 + - name: '{{ vlan1 }}' + group: + - virtual_router_id: 12 + afi: ipv6 + virtual_address: + - address: 81::15 + - address: 81::16 + track_interface: + - interface: '{{ interface1 }}' + priority_increment: 15 + - name: test_case_03 + description: Update VRRP and VRRP6 configuration + state: merged + input: + - name: '{{ vlan1 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 81.1.1.4 + - address: 81.1.1.5 + preempt: False + track_interface: + - interface: '{{ interface5 }}' + priority_increment: 25 + - name: '{{ po1 }}' + group: + - virtual_router_id: 25 + afi: ipv4 + virtual_address: + - address: 101.1.1.3 + priority: 100 + - virtual_router_id: 15 + afi: ipv6 + virtual_address: + - address: 101::3 + - name: '{{ po2 }}' + group: + - virtual_router_id: 20 + afi: ipv4 + virtual_address: + - address: 102.1.1.3 + - virtual_router_id: 15 + afi: ipv6 + virtual_address: + - address: 102::3 + advertisement_interval: 10 + - name: '{{ vlan2 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 91.1.1.3 + - address: 91.1.1.5 + preempt: True + - virtual_router_id: 2 + afi: ipv6 + virtual_address: + - address: 91::3 + - address: 91::5 + - address: 91::7 + advertisement_interval: 15 + - name: test_case_04 + description: Delete VRRP and VRRP6 configuration + state: deleted + input: + - name: '{{ vlan1 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 81.1.1.5 + preempt: False + - name: '{{ po2 }}' + group: + - virtual_router_id: 20 + afi: ipv4 + - virtual_router_id: 15 + afi: ipv6 + advertisement_interval: 10 + virtual_address: + - address: 102::3 + - name: '{{ interface1 }}' + group: + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 80::3 + - address: 80::4 + advertisement_interval: 4 + priority: 10 + - name: '{{ interface2 }}' + group: + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 90.1.1.4 + preempt: True + - name: test_case_05 + description: Delete VRRP and VRRP6 configuration + state: deleted + input: + - name: '{{ interface1 }}' + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 80.1.1.4 + - virtual_router_id: 15 + afi: ipv4 + track_interface: + - interface: '{{ interface5 }}' + priority_increment: 10 + - name: '{{ vlan1 }}' + group: + - virtual_router_id: 12 + afi: ipv6 + track_interface: + - interface: '{{ interface1 }}' + priority_increment: 15 + - name: test_case_replace_01 + description: Replace VRRP and VRRP6 configuration by VRRP/VRRP6 groups + state: replaced + input: + - name: '{{ vlan2 }}' + group: + - virtual_router_id: 2 + afi: ipv6 + preempt: False + - virtual_router_id: 10 + afi: ipv4 + version: 2 + use_v2_checksum: True + - name: '{{ interface1 }}' + group: + - virtual_router_id: 15 + afi: ipv4 + - name: test_case_replace_02 + description: Replace VRRP and VRRP6 configuration + state: replaced + input: + - name: '{{ interface1 }}' + group: + - virtual_router_id: 10 + afi: ipv4 + preempt: False + priority: 20 + - virtual_router_id: 13 + afi: ipv6 + - name: test_case_over_01 + description: Override VRRP and VRRP6 configuration + state: overridden + input: + - name: '{{ po1 }}' + group: + - virtual_router_id: 11 + afi: ipv6 + virtual_address: + - address: 101::11 + track_interface: + - interface: '{{ po2 }}' + priority_increment: 10 + - name: '{{ po2 }}' + group: + - virtual_router_id: 15 + afi: ipv6 + virtual_address: + - address: 102::15 + - address: 102::16 + advertisement_interval: 20 + - name: '{{ vlan2 }}' + group: + - virtual_router_id: 20 + afi: ipv4 + virtual_address: + - address: 91.1.1.4 + preempt: False + - name: test_case_06 + description: Delete all VRRP and VRRP6 configurations for specified interface + state: deleted + input: + - name: '{{ po1 }}' + group: [] + - name: '{{ po2 }}' + group: [] + - name: test_case_07 + description: Delete all VRRP and VRRP6 configurations + state: deleted + input: [] diff --git a/tests/regression/roles/sonic_vrrp/meta/main.yml b/tests/regression/roles/sonic_vrrp/meta/main.yml new file mode 100644 index 000000000..78f79f8ca --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } \ No newline at end of file diff --git a/tests/regression/roles/sonic_vrrp/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_vrrp/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..62ad2507c --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/tasks/cleanup_tests.yaml @@ -0,0 +1,30 @@ +--- +- name: Delete VRRP, VRRP6 configurations + dellemc.enterprise_sonic.sonic_vrrp: + config: [] + state: deleted + ignore_errors: yes + +- name: Delete l3 configurations on test interfaces + dellemc.enterprise_sonic.sonic_l3_interfaces: + config: "{{ preparations_tests.l3_interfaces }}" + state: deleted + ignore_errors: yes + +- name: Delete test VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: deleted + ignore_errors: yes + +- name: Delete test VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ preparations_tests.vlans }}" + state: deleted + ignore_errors: yes + +- name: Delete test lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ preparations_tests.lag_interfaces }}" + state: deleted + ignore_errors: yes \ No newline at end of file diff --git a/tests/regression/roles/sonic_vrrp/tasks/main.yml b/tests/regression/roles/sonic_vrrp/tasks/main.yml new file mode 100644 index 000000000..0cacd7760 --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- ansible.builtin.debug: + msg: "sonic_vrrp Test started ..." + +- name: "Preparations for {{ module_name }}" + ansible.builtin.include_tasks: preparation_tests.yaml + +- name: "Test {{ module_name }} started" + ansible.builtin.include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: "Cleanup of {{ module_name }}" + ansible.builtin.include_tasks: cleanup_tests.yaml diff --git a/tests/regression/roles/sonic_vrrp/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_vrrp/tasks/preparation_tests.yaml new file mode 100644 index 000000000..d387b68cb --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/tasks/preparation_tests.yaml @@ -0,0 +1,38 @@ +--- +- name: Delete old VRRP, VRRP6 configurations + dellemc.enterprise_sonic.sonic_vrrp: + config: [] + state: deleted + ignore_errors: yes + +- name: Initialize default interfaces + vars: + ansible_connection: network_cli + dellemc.enterprise_sonic.sonic_config: + commands: "{{ default_interface_cli }}" + register: output + ignore_errors: yes + +- name: Create VRFs + dellemc.enterprise_sonic.sonic_vrfs: + config: "{{ preparations_tests.vrfs }}" + state: merged + ignore_errors: yes + +- name: Create lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ preparations_tests.lag_interfaces }}" + state: merged + ignore_errors: yes + +- name: Create VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ preparations_tests.vlans }}" + state: merged + ignore_errors: yes + +- name: Configure l3 interfaces + dellemc.enterprise_sonic.sonic_l3_interfaces: + config: "{{ preparations_tests.l3_interfaces }}" + state: merged + ignore_errors: yes \ No newline at end of file diff --git a/tests/regression/roles/sonic_vrrp/tasks/tasks_template.yaml b/tests/regression/roles/sonic_vrrp/tasks/tasks_template.yaml new file mode 100644 index 000000000..1b7f868cb --- /dev/null +++ b/tests/regression/roles/sonic_vrrp/tasks/tasks_template.yaml @@ -0,0 +1,22 @@ +--- +- name: "{{ item.name }} , {{ item.description }}" + dellemc.enterprise_sonic.sonic_vrrp: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + dellemc.enterprise_sonic.sonic_vrrp: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- ansible.builtin.import_role: + name: common + tasks_from: idempotent.facts.report.yaml \ No newline at end of file diff --git a/tests/regression/test.yaml b/tests/regression/test.yaml index 07dd74674..87c657c77 100644 --- a/tests/regression/test.yaml +++ b/tests/regression/test.yaml @@ -26,13 +26,17 @@ - sonic_bgp_af - sonic_bgp_neighbors - sonic_bgp_neighbors_af + - sonic_ospfv2_interfaces + - sonic_ospfv2 - sonic_dhcp_snooping - sonic_vlan_mapping - sonic_vrfs + - sonic_vrrp - sonic_vxlan - sonic_port_breakout - sonic_users - sonic_aaa + - sonic_ldap - sonic_tacacs_server - sonic_radius_server - sonic_prefix_lists @@ -64,5 +68,7 @@ - sonic_pim_global - sonic_pim_interfaces - sonic_login_lockout + - sonic_ospf_area - sonic_poe + - sonic_mgmt_servers - test_reports diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml index e7ca4429a..9d44f4f4a 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_aaa.yaml @@ -2,77 +2,496 @@ merged_01: module_args: config: - authentication: - data: - local: true - fail_through: true - group: radius + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' response: code: 200 - - path: "data/sonic-system-aaa:sonic-system-aaa" + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + expected_config_requests: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: + config: + authorization-method: + - local + - tacacs+ + openconfig-aaa-ext:login: + config: + authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local + +replaced_01: + module_args: + config: + authentication: + console_auth_local: True + authorization: + commands_auth_method: + - local + name_service: + group: + - local + state: replaced + existing_aaa_config: + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' response: code: 200 value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa" - method: "patch" + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' data: - openconfig-system:aaa: - authentication: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + console-authentication-local: True + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: config: - authentication-method: + authorization-method: - local - - radius - failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - local + +overridden_01: + module_args: + config: + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local + state: overridden + existing_aaa_config: + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + console-authentication-local: False + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: + value: + openconfig-aaa-ext:config: + group-method: + - local + expected_config_requests: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'patch' + data: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'patch' + data: + openconfig-system:authorization: + openconfig-aaa-tacacsplus-ext:commands: + config: + authorization-method: + - local + - tacacs+ + openconfig-aaa-ext:login: + config: + authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'patch' + data: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local + deleted_01: module_args: + config: + authentication: + auth_method: + - local + - ldap + - radius + - tacacs+ + console_auth_local: True + failthrough: True + authorization: + commands_auth_method: + - local + - tacacs+ + login_auth_method: + - local + - ldap + name_service: + group: + - ldap + - local + - login + netgroup: + - ldap + - local + passwd: + - ldap + - local + - login + shadow: + - ldap + - local + - login + sudoers: + - ldap + - local state: deleted existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' + response: + code: 200 + value: + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' response: code: 200 value: - openconfig-system:aaa: - authentication: - config: - authentication-method: - - radius - - local - failthrough: true + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa/authentication/config/authentication-method" - method: "delete" + - path: '/data/openconfig-system:system/aaa/authentication/config/authentication-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config/console-authentication-local' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authentication/config/failthrough' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + method: 'delete' data: - - path: "data/openconfig-system:system/aaa/authentication/config/failthrough" - method: "delete" + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/group-method' + method: 'delete' data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/netgroup-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/passwd-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/shadow-method' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config/sudoers-method' + method: 'delete' + data: + deleted_02: module_args: - config: - authentication: - data: - local: true - fail_through: true - group: radius + config: {} state: deleted existing_aaa_config: - - path: "data/openconfig-system:system/aaa" + - path: '/data/openconfig-system:system/aaa/authentication/config' response: code: 200 value: - openconfig-system:aaa: - authentication: - config: - authentication-method: - - radius - - local - failthrough: true + openconfig-system:config: + authentication-method: + - local + - ldap + - radius + - tacacs+ + console-authentication-local: True + failthrough: 'True' + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-tacacsplus-ext:commands/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-tacacsplus-ext:authorization-method: + - local + - tacacs+ + - path: '/data/openconfig-system:system/aaa/authorization/openconfig-aaa-ext:login/config/authorization-method' + response: + code: 200 + value: + openconfig-aaa-ext:authorization-method: + - local + - ldap + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + response: + code: 200 + value: + openconfig-aaa-ext:config: + group-method: + - ldap + - local + - login + netgroup-method: + - ldap + - local + passwd-method: + - ldap + - local + - login + shadow-method: + - ldap + - local + - login + sudoers-method: + - ldap + - local expected_config_requests: - - path: "data/openconfig-system:system/aaa/authentication/config/authentication-method" - method: "delete" + - path: '/data/openconfig-system:system/aaa/authentication/config' + method: 'delete' + data: + - path: '/data/openconfig-system:system/aaa/authorization' + method: 'delete' data: - - path: "data/openconfig-system:system/aaa/authentication/config/failthrough" - method: "delete" + - path: '/data/openconfig-system:system/aaa/openconfig-aaa-ext:name-service/config' + method: 'delete' data: diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_bgp.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_bgp.yaml index 1c5d59fba..0c56f0468 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_bgp.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_bgp.yaml @@ -6,6 +6,7 @@ merged_01: router_id: 10.2.2.4 rt_delay: 10 log_neighbor_changes: False + as_notation: "asdot+" timers: holdtime: 20 keepalive_interval: 30 @@ -24,6 +25,10 @@ merged_01: on_startup: timer: 667 med_val: 7878 + - bgp_as: 10.5 + router_id: 10.2.2.5 + as_notation: "asdot" + vrf_name: "VrfReg1" existing_bgp_config: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global" response: @@ -32,12 +37,20 @@ merged_01: openconfig-network-instance:global: config: as: 4 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 655365 - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" response: code: 200 value: sonic-vrf:VRF_LIST: - vrf_name: default + - vrf_name: VrfReg1 expected_config_requests: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" method: "patch" @@ -46,6 +59,7 @@ merged_01: router-id: "10.2.2.4" as: 4.0 route-map-process-delay: 10 + as-notation: "ASDOT_PLUS" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config/hold-time" method: "patch" data: @@ -77,6 +91,13 @@ merged_01: data: openconfig-network-instance:config: as-set: True + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/config" + method: "patch" + data: + openconfig-network-instance:config: + router-id: "10.2.2.5" + as: "10.5" + as-notation: "ASDOT" deleted_01: module_args: config: @@ -84,6 +105,7 @@ deleted_01: router_id: 10.2.2.4 rt_delay: 10 log_neighbor_changes: False + as_notation: "asdot+" bestpath: as_path: confed: True @@ -115,6 +137,7 @@ deleted_01: route-map-process-delay: 10 hold-time: 20 keepalive-interval: 30 + as-notation: "ASDOT_PLUS" logging-options: config: log-neighbor-state-changes: False @@ -148,6 +171,8 @@ deleted_01: method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config/router-id" method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config/as-notation" + method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config/route-map-process-delay" method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/max-med/config/max-med-val" @@ -187,6 +212,7 @@ deleted_02: route-map-process-delay: 10 hold-time: 20 keepalive-interval: 30 + as-notation: "ASDOT" logging-options: config: log-neighbor-state-changes: False @@ -224,6 +250,7 @@ replaced_01: keepalive_interval: 30 - bgp_as: 4 router_id: 10.2.2.4 + as_notation: 'asdot+' max_med: on_startup: timer: 776 @@ -241,6 +268,7 @@ replaced_01: route-map-process-delay: 10 hold-time: 20 keepalive-interval: 30 + as-notation: "ASDOT" logging-options: config: log-neighbor-state-changes: False @@ -307,6 +335,7 @@ replaced_01: data: openconfig-network-instance:config: as: 4.0 + as-notation: "ASDOT_PLUS" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/max-med" method: "patch" data: @@ -350,6 +379,7 @@ overridden_01: - bgp_as: 4 router_id: 10.2.2.5 rt_delay: 10 + as_notation: "asdot" bestpath: as_path: confed: True @@ -375,6 +405,7 @@ overridden_01: route-map-process-delay: 10 hold-time: 180 keepalive-interval: 60 + as-notation: "ASDOT_PLUS" logging-options: config: log-neighbor-state-changes: True @@ -443,6 +474,7 @@ overridden_01: openconfig-network-instance:config: router-id: "10.2.2.5" as: 4.0 + as-notation: "ASDOT" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/max-med" method: "patch" data: diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_bgp_af.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_bgp_af.yaml index 08b95f27f..26556971a 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_bgp_af.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_bgp_af.yaml @@ -28,6 +28,11 @@ merged_01: - 2.2.2.2/16 - 192.168.10.1/32 dampening: True + aggregate_address_config: + - prefix: "1.1.1.1/1" + as_set: True + policy_name: aaa + summary_only: True - afi: ipv6 safi: unicast dampening: True @@ -190,6 +195,17 @@ merged_01: route-flap-damping: config: enabled: True + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/aggregate-address-config" + method: "patch" + data: + openconfig-network-instance:aggregate-address-config: + aggregate-address: + - prefix: '1.1.1.1/1' + config: + prefix: '1.1.1.1/1' + as-set: True + policy-name: 'aaa' + summary-only: True - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV6_UNICAST/use-multiple-paths" method: "patch" data: @@ -768,6 +784,12 @@ deleted_01: - metric: "25" protocol: static route_map: aa + aggregate_address_config: + - prefix: "1.1.1.1/1" + - prefix: "2.2.2.2/2" + as_set: True + policy_name: aa + summary_only: True - afi: ipv6 safi: unicast max_path: @@ -855,6 +877,17 @@ deleted_01: ibgp: config: maximum-paths: 2 + aggregate-address-config: + aggregate-address: + - prefix: '1.1.1.1/1' + config: + prefix: '1.1.1.1/1' + - prefix: '2.2.2.2/2' + config: + prefix: '2.2.2.2/2' + as-set: True + policy-name: aa + summary-only: True - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST config: afi-safi-name: openconfig-bgp-types:IPV6_UNICAST @@ -969,6 +1002,14 @@ deleted_01: method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/use-multiple-paths/ibgp/config/maximum-paths" method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/aggregate-address-config/aggregate-address=1.1.1.1%2F1" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/aggregate-address-config/aggregate-address=2.2.2.2%2F2/config/as-set" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/aggregate-address-config/aggregate-address=2.2.2.2%2F2/config/policy-name" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/aggregate-address-config/aggregate-address=2.2.2.2%2F2/config/summary-only" + method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV6_UNICAST/use-multiple-paths/ebgp/config/maximum-paths" method: "delete" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV6_UNICAST/use-multiple-paths/ibgp/config/maximum-paths" @@ -1435,3 +1476,588 @@ deleted_03: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:STATIC,openconfig-policy-types:BGP,openconfig-types:IPV6" method: "delete" data: + +replaced_01: + module_args: + config: + - bgp_as: 51 + address_family: + afis: + - afi: l2vpn + safi: evpn + advertise_pip: True + advertise_pip_ip: "3.3.3.3" + advertise_pip_peer_ip: "4.4.4.4" + advertise_svi_ip: True + advertise_all_vni: False + advertise_default_gw: False + route_advertise_list: + - advertise_afi: ipv4 + route_map: bb + rd: "1.1.1.1:11" + rt_in: + - "22:22" + rt_out: + - "13:13" + vnis: + - vni_number: 5 + advertise_default_gw: True + advertise_svi_ip: True + rd: "10.10.10.10:55" + rt_in: + - "88:88" + rt_out: + - "77:77" + - afi: ipv4 + safi: unicast + network: + - 2.2.2.2/16 + - 192.168.10.1/32 + dampening: True + redistribute: + - protocol: connected + - protocol: ospf + metric: 30 + - bgp_as: 51 + vrf_name: VrfReg2 + address_family: + afis: + - afi: ipv4 + safi: unicast + import: + vrf: + vrf_list: + - VrfReg1 + route_map: rmap-reg1 + state: replaced + existing_bgp_config: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 51 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + route-flap-damping: + config: + enabled: true + use-multiple-paths: + ebgp: + config: + maximum-paths: 1 + ibgp: + config: + maximum-paths: 1 + network-config: + network: + - prefix: '2.2.2.2/16' + config: + prefix: '2.2.2.2/16' + - prefix: '192.168.10.1/32' + config: + prefix: '192.168.10.1/32' + - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + use-multiple-paths: + ebgp: + config: + maximum-paths: 4 + ibgp: + config: + maximum-paths: 5 + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + config: + afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:config: + advertise-all-vni: true + advertise-pip: true + advertise-pip-ip: '3.3.3.3' + advertise-pip-peer-ip: '4.4.4.4' + advertise-svi-ip: true + route-distinguisher: '1.1.1.1:11' + import-rts: + - '12:12' + export-rts: + - '13:13' + openconfig-bgp-evpn-ext:route-advertise: + route-advertise-list: + - advertise-afi-safi: openconfig-bgp-types:IPV4_UNICAST + config: + advertise-afi-safi: openconfig-bgp-types:IPV4_UNICAST + route-map: + - aa + openconfig-bgp-evpn-ext:vnis: + vni: + - config: + vni-number: 1 + route-distinguisher: '5.5.5.5:55' + advertise-default-gw: true + advertise-svi-ip: true + import-rts: + - '88:88' + export-rts: + - '77:77' + vni-number: 1 + openconfig-bgp-evpn-ext:dup-addr-detection: + config: + enabled: true + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 51 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + network-config: + network: + - prefix: '3.3.3.3/16' + config: + prefix: '3.3.3.3/16' + route-flap-damping: + config: + enabled: true + use-multiple-paths: + ebgp: + config: + maximum-paths: 1 + ibgp: + config: + maximum-paths: 1 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 51 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + openconfig-bgp-ext:import-network-instance: + config: + name: + - default + policy-name: rmap-1 + route-flap-damping: + config: + enabled: false + use-multiple-paths: + ebgp: + config: + maximum-paths: 1 + ibgp: + config: + maximum-paths: 1 + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/table-connections" + response: + code: 200 + value: + openconfig-network-instance:table-connections: + table-connection: + - src-protocol: openconfig-policy-types:DIRECTLY_CONNECTED + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + config: + src-protocol: openconfig-policy-types:DIRECTLY_CONNECTED + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + metric: 21 + import-policy: + - bb + - src-protocol: openconfig-policy-types:OSPF + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + config: + src-protocol: openconfig-policy-types:OSPF + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + metric: 27 + import-policy: + - bb + - src-protocol: openconfig-policy-types:STATIC + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV6 + config: + src-protocol: openconfig-policy-types:STATIC + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV6 + metric: 26 + import-policy: + - aa + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/table-connections" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/table-connections" + response: + code: 200 + expected_config_requests: + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:config/import-rts=12%3A12 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:route-advertise/route-advertise-list=IPV4_UNICAST/config/route-map=aa + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:vnis/vni=1 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:DIRECTLY_CONNECTED,openconfig-policy-types:BGP,openconfig-types:IPV4/config/import-policy=bb + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:DIRECTLY_CONNECTED,openconfig-policy-types:BGP,openconfig-types:IPV4/config/metric + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:OSPF,openconfig-policy-types:BGP,openconfig-types:IPV4/config/import-policy=bb + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/openconfig-bgp-ext:import-network-instance/config/name=default + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/openconfig-bgp-ext:import-network-instance + method: patch + data: + openconfig-bgp-ext:import-network-instance: + config: + name: + - VrfReg1 + policy-name: rmap-reg1 + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global + method: patch + data: + openconfig-network-instance:global: + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:config: + advertise-all-vni: false + advertise-default-gw: false + import-rts: + - '22:22' + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global + method: patch + data: + openconfig-network-instance:global: + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:vnis: + vni: + - config: + advertise-default-gw: true + advertise-svi-ip: true + export-rts: + - 77:77 + import-rts: + - 88:88 + route-distinguisher: 10.10.10.10:55 + vni-number: 5 + vni-number: 5 + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:route-advertise/route-advertise-list + method: patch + data: + openconfig-bgp-evpn-ext:route-advertise-list: + - advertise-afi-safi: IPV4_UNICAST + config: + advertise-afi-safi: IPV4_UNICAST + route-map: + - bb + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections + method: patch + data: + openconfig-network-instance:table-connections: + table-connection: + - address-family: openconfig-types:IPV4 + config: + address-family: openconfig-types:IPV4 + metric: 30.0 + dst-protocol: openconfig-policy-types:BGP + src-protocol: openconfig-policy-types:OSPF + +overridden_01: + module_args: + config: + - bgp_as: 51 + address_family: + afis: + - afi: l2vpn + safi: evpn + advertise_pip: True + advertise_pip_ip: "3.3.3.3" + advertise_pip_peer_ip: "4.4.4.4" + advertise_svi_ip: True + advertise_all_vni: False + advertise_default_gw: False + route_advertise_list: + - advertise_afi: ipv4 + route_map: bb + rd: "1.1.1.1:11" + rt_in: + - "22:22" + rt_out: + - "13:13" + vnis: + - vni_number: 5 + advertise_default_gw: True + advertise_svi_ip: True + rd: "10.10.10.10:55" + rt_in: + - "88:88" + rt_out: + - "77:77" + - afi: ipv4 + safi: unicast + network: + - 2.2.2.2/16 + - 192.168.10.1/32 + dampening: True + redistribute: + - protocol: connected + - protocol: ospf + metric: 30 + state: overridden + existing_bgp_config: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 51 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + route-flap-damping: + config: + enabled: true + use-multiple-paths: + ebgp: + config: + maximum-paths: 1 + ibgp: + config: + maximum-paths: 1 + network-config: + network: + - prefix: '2.2.2.2/16' + config: + prefix: '2.2.2.2/16' + - prefix: '192.168.10.1/32' + config: + prefix: '192.168.10.1/32' + - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + use-multiple-paths: + ebgp: + config: + maximum-paths: 4 + ibgp: + config: + maximum-paths: 5 + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + config: + afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:config: + advertise-all-vni: true + advertise-pip: true + advertise-pip-ip: '3.3.3.3' + advertise-pip-peer-ip: '4.4.4.4' + advertise-svi-ip: true + route-distinguisher: '1.1.1.1:11' + import-rts: + - '12:12' + export-rts: + - '13:13' + openconfig-bgp-evpn-ext:route-advertise: + route-advertise-list: + - advertise-afi-safi: openconfig-bgp-types:IPV4_UNICAST + config: + advertise-afi-safi: openconfig-bgp-types:IPV4_UNICAST + route-map: + - bb + openconfig-bgp-evpn-ext:vnis: + vni: + - config: + vni-number: 1 + route-distinguisher: '5.5.5.5:55' + advertise-default-gw: true + advertise-svi-ip: true + import-rts: + - '88:88' + export-rts: + - '77:77' + vni-number: 1 + openconfig-bgp-evpn-ext:dup-addr-detection: + config: + enabled: true + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global" + response: + code: 200 + value: + openconfig-network-instance:global: + config: + as: 51 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + openconfig-bgp-ext:import-network-instance: + config: + name: + - default + policy-name: rmap-1 + network-config: + network: + - prefix: '3.3.3.3/16' + config: + prefix: '3.3.3.3/16' + route-flap-damping: + config: + enabled: true + use-multiple-paths: + ebgp: + config: + maximum-paths: 1 + ibgp: + config: + maximum-paths: 1 + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/table-connections" + response: + code: 200 + value: + openconfig-network-instance:table-connections: + table-connection: + - src-protocol: openconfig-policy-types:DIRECTLY_CONNECTED + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + config: + src-protocol: openconfig-policy-types:DIRECTLY_CONNECTED + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + metric: 21 + import-policy: + - bb + - src-protocol: openconfig-policy-types:OSPF + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + config: + src-protocol: openconfig-policy-types:OSPF + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV4 + metric: 27 + import-policy: + - bb + - src-protocol: openconfig-policy-types:STATIC + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV6 + config: + src-protocol: openconfig-policy-types:STATIC + dst-protocol: openconfig-policy-types:BGP + address-family: openconfig-types:IPV6 + metric: 26 + import-policy: + - aa + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/table-connections" + response: + code: 200 + expected_config_requests: + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV6_UNICAST/use-multiple-paths/ebgp/config/maximum-paths + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV6_UNICAST/use-multiple-paths/ibgp/config/maximum-paths + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:config/import-rts=12%3A12 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=L2VPN_EVPN/l2vpn-evpn/openconfig-bgp-evpn-ext:vnis/vni=1 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=openconfig-bgp-types:IPV6_UNICAST + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/network-config/network=3.3.3.3%2f16 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/openconfig-bgp-ext:import-network-instance/config/name + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/openconfig-bgp-ext:import-network-instance/config/policy-name + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=IPV4_UNICAST/route-flap-damping/config/enabled + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/afi-safis/afi-safi=openconfig-bgp-types:IPV4_UNICAST + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:DIRECTLY_CONNECTED,openconfig-policy-types:BGP,openconfig-types:IPV4/config/import-policy=bb + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:DIRECTLY_CONNECTED,openconfig-policy-types:BGP,openconfig-types:IPV4/config/metric + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:OSPF,openconfig-policy-types:BGP,openconfig-types:IPV4/config/import-policy=bb + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections/table-connection=openconfig-policy-types:STATIC,openconfig-policy-types:BGP,openconfig-types:IPV6 + method: delete + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global + method: patch + data: + openconfig-network-instance:global: + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:config: + advertise-all-vni: false + advertise-default-gw: false + import-rts: + - '22:22' + - path: /data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global + method: patch + data: + openconfig-network-instance:global: + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + l2vpn-evpn: + openconfig-bgp-evpn-ext:vnis: + vni: + - config: + vni-number: 5 + advertise-default-gw: true + advertise-svi-ip: true + route-distinguisher: 10.10.10.10:55 + import-rts: + - 88:88 + export-rts: + - 77:77 + vni-number: 5 + + - path: /data/openconfig-network-instance:network-instances/network-instance=default/table-connections + method: patch + data: + openconfig-network-instance:table-connections: + table-connection: + - address-family: openconfig-types:IPV4 + dst-protocol: openconfig-policy-types:BGP + src-protocol: openconfig-policy-types:OSPF + config: + address-family: openconfig-types:IPV4 + metric: 30.0 diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_bgp_neighbors.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_bgp_neighbors.yaml index 688c9269a..bf35752e2 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_bgp_neighbors.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_bgp_neighbors.yaml @@ -104,6 +104,20 @@ merged_01: strict_capability_match: true v6only: true - neighbor: 192.168.1.4 + - bgp_as: 51 + vrf_name: VrfReg2 + peer_group: + - name: SPINE + remote_as: + peer_as: "10.4" + local_as: + as: "200.5" + neighbors: + - neighbor: Eth1/3 + remote_as: + peer_as: "20.4" + local_as: + as: "100.5" existing_bgp_config: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" response: @@ -117,6 +131,12 @@ merged_01: value: openconfig-network-instance:config: as: 51 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" response: code: 200 @@ -129,6 +149,12 @@ merged_01: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" response: code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" response: code: 200 @@ -136,6 +162,7 @@ merged_01: sonic-vrf:VRF_LIST: - vrf_name: default - vrf_name: VrfReg1 + - vrf_name: VrfReg2 expected_config_requests: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/neighbors" method: "patch" @@ -179,7 +206,7 @@ merged_01: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" method: "patch" data: - openconfig-network-instance:peer-groups: + openconfig-network-instance:peer-groups: peer-group: - peer-group-name: SPINE auth-password: @@ -244,6 +271,32 @@ merged_01: shutdown-message: msg1 strict-capability-match: True ttl-security-hops: 5 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - neighbor-address: Eth1/3 + transport: + config: + passive-mode: False + config: + neighbor-address: Eth1/3 + peer-as: "20.4" + local-as: "100.5" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + method: "patch" + data: + openconfig-network-instance:peer-groups: + peer-group: + - peer-group-name: SPINE + transport: + config: + passive-mode: False + config: + peer-group-name: SPINE + peer-as: "10.4" + local-as: "200.5" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" method: "patch" data: @@ -396,6 +449,30 @@ merged_02: peer_as: 700 peer_group: SPINE - neighbor: 192.168.1.4 + - bgp_as: 51 + vrf_name: VrfReg2 + peer_group: + - name: SPINE + remote_as: + peer_as: "10.4" + local_as: + as: "200.5" + - name: SPINE2 + remote_as: + peer_as: 655366 + local_as: + as: "200.7" + neighbors: + - neighbor: Eth1/3 + remote_as: + peer_as: "20.4" + local_as: + as: "100.5" + - neighbor: 192.168.1.4 + remote_as: + peer_as: "20.6" + local_as: + as: 6553607 existing_bgp_config: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" response: @@ -409,6 +486,12 @@ merged_02: value: openconfig-network-instance:config: as: 51 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" response: code: 200 @@ -427,12 +510,18 @@ merged_02: peer-group: SPINE local-as: 51 peer-as: 65399 + transport: + config: + passive-mode: False - neighbor-address: Eth1/4 config: neighbor-address: Eth1/4 peer-group: SPINE local-as: 51 peer-type: INTERNAL + transport: + config: + passive-mode: False - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" response: code: 200 @@ -442,6 +531,9 @@ merged_02: - config: peer-group-name: SPINE peer-type: INTERNAL + transport: + config: + passive-mode: False timers: config: connect-retry: 30 @@ -449,6 +541,49 @@ merged_02: - config: peer-group-name: SPINE5 peer-as: 55 + transport: + config: + passive-mode: False + timers: + config: + connect-retry: 40 + minimum-advertisement-interval: 50 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + value: + openconfig-network-instance:neighbors: + neighbor: + - neighbor-address: Eth1/3 + config: + neighbor-address: Eth1/3 + local-as: 51 + peer-as: 399 + - neighbor-address: 192.168.1.4 + config: + neighbor-address: 192.168.1.4 + peer-as: 1310726 + local-as: "100.7" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + value: + openconfig-network-instance:peer-groups: + peer-group: + - peer-group-name: SPINE + config: + peer-group-name: SPINE + local-as: 55 + peer-as: "222.22" + timers: + config: + connect-retry: 40 + minimum-advertisement-interval: 50 + - peer-group-name: SPINE2 + config: + peer-group-name: SPINE2 + peer-as: "10.6" + local-as: 13107207 timers: config: connect-retry: 40 @@ -460,6 +595,7 @@ merged_02: sonic-vrf:VRF_LIST: - vrf_name: default - vrf_name: VrfReg1 + - vrf_name: VrfReg2 expected_config_requests: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/neighbors" method: "patch" @@ -509,7 +645,7 @@ merged_02: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" method: "patch" data: - openconfig-network-instance:peer-groups: + openconfig-network-instance:peer-groups: peer-group: - peer-group-name: SPINE auth-password: @@ -584,6 +720,26 @@ merged_02: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINE5/config/peer-as" method: "delete" data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - neighbor-address: Eth1/3 + config: + neighbor-address: Eth1/3 + peer-as: "20.4" + local-as: "100.5" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + method: "patch" + data: + openconfig-network-instance:peer-groups: + peer-group: + - peer-group-name: SPINE + config: + peer-group-name: SPINE + peer-as: "10.4" + local-as: "200.5" - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" method: "patch" data: @@ -773,7 +929,7 @@ deleted_02: bfd: enabled: true check_failure: true - profile: 'kvsk_bfd_profile' + profile: 'bfd_profile' ebgp_multihop: enabled: true multihop_ttl: 22 @@ -782,18 +938,18 @@ deleted_02: pwd: 'U2FsdGVkX1+LHXncDf0uAxQrs4CN7H5yDKT5sht6Ga4=' enforce_first_as: true enforce_multihop: true - pg_description: 'pg_kvsk_description' + pg_description: 'pg_description' disable_connected_check: true dont_negotiate_capability: true - local_as: + local_as: as: 65299 no_prepend: true replace_as: true override_capability: true - shutdown_msg: pg_kvsk_shutdown_msg + shutdown_msg: pg_shutdown_msg passive: true local_address: 5.5.5.5 - solo: true + solo: true address_family: afis: - afi: ipv4 @@ -801,6 +957,8 @@ deleted_02: activate: true allowas_in: value: 8 + ip_afi: + send_default_route: true - afi: ipv6 safi: unicast activate: true @@ -819,6 +977,20 @@ deleted_02: neighbors: - neighbor: 1.1.1.1 - neighbor: Eth1/2 + - bgp_as: 51 + vrf_name: VrfReg2 + peer_group: + - name: SPINE + remote_as: + peer_as: "10.4" + local_as: + as: "200.5" + neighbors: + - neighbor: Eth1/3 + remote_as: + peer_as: 1310724 + local_as: + as: "100.5" state: deleted existing_bgp_config: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" @@ -835,6 +1007,41 @@ deleted_02: openconfig-network-instance:config: as: 51 router-id: "10.2.2.4" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.5" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + value: + openconfig-network-instance:neighbors: + neighbor: + - neighbor-address: Eth1/3 + config: + description: "nbr_asdot_description" + neighbor-address: Eth1/3 + peer-as: "20.4" + local-as: 101 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + value: + openconfig-network-instance:peer-groups: + peer-group: + - peer-group-name: SPINE + timers: + config: + connect-retry: 30 + minimum-advertisement-interval: 0 + config: + description: "pg_asdot_description" + peer-group-name: SPINE + peer-as: "0.102" + local-as: 13107205 - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" response: code: 200 @@ -842,6 +1049,7 @@ deleted_02: sonic-vrf:VRF_LIST: - vrf_name: default - vrf_name: VrfReg1 + - vrf_name: VrfReg2 - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" response: code: 200 @@ -853,29 +1061,29 @@ deleted_02: capability-extended-nexthop: true enabled: true peer-group-name: SPINETEST1 - description: "pg_kvsk_description" + description: "pg_description" disable-ebgp-connected-route-check: true dont-negotiate-capability: true enforce-first-as: true enforce-multihop: true local-as: 65299 - shutdown-message: pg_kvsk_shutdown_msg + shutdown-message: pg_shutdown_msg local-as-no-prepend: true local-as-replace-as: true override-capability: true peer-as: 65399 - solo-peer: true + solo-peer: true peer-group-name: SPINETEST1 enable-bfd: config: enabled: true check-control-plane-failure: true - bfd-profile: 'kvsk_bfd_profile' + bfd-profile: 'bfd_profile' ebgp-multihop: config: enabled: true multihop-ttl: 22 - auth-password: + auth-password: config: encrypted: true password: 'U2FsdGVkX1+LHXncDf0uAxQrs4CN7H5yDKT5sht6Ga4=' @@ -887,7 +1095,7 @@ deleted_02: connect-retry: 11 transport: config: - passive-mode: true + passive-mode: true local-address: 5.5.5.5 afi-safis: afi-safi: @@ -897,6 +1105,9 @@ deleted_02: as-count: 8 config: afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + ipv4-unicast: + config: + send-default-route: true - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST ipv6-unicast: prefix-limit: @@ -1011,6 +1222,9 @@ deleted_02: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINETEST1/afi-safis/afi-safi=openconfig-bgp-types:IPV4_UNICAST/allow-own-as/config/as-count" method: "delete" data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINETEST1/afi-safis/afi-safi=openconfig-bgp-types:IPV4_UNICAST/ipv4-unicast/config/send-default-route" + method: "delete" + data: - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINETEST1/afi-safis/afi-safi=openconfig-bgp-types:IPV6_UNICAST/allow-own-as/config/origin" method: "delete" data: @@ -1116,3 +1330,586 @@ deleted_02: - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=Eth1%2f2/" method: "delete" data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=Eth1%2f3/config/peer-as" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINE/config/local-as" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINE/config/local-as-no-prepend" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINE/config/local-as-replace-as" + method: "delete" + data: + +replaced_01: + module_args: + config: + - bgp_as: 51 + neighbors: + - neighbor: "Eth1/3" + advertisement_interval: 10 + remote_as: + peer_as: 15 + - neighbor: "1.1.1.1" + ttl_security: 5 + advertisement_interval: 20 + disable_connected_check: true + passive: False + peer_group: + - name: "SPINE1" + address_family: + afis: + - afi: "ipv4" + allowas_in: + origin: true + safi: "unicast" + advertisement_interval: 20 + - bgp_as: 51 + vrf_name: "VrfReg1" + neighbors: + - neighbor: "Eth1/1" + capability: + dynamic: true + bfd: + check_failure: true + enabled: true + profile: "profile 2" + state: replaced + existing_bgp_config: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + value: + openconfig-network-instance:peer-groups: + peer-group: + - config: + capability-dynamic: true + capability-extended-nexthop: true + enabled: true + peer-group-name: SPINETEST1 + description: "pg_description" + disable-ebgp-connected-route-check: true + dont-negotiate-capability: true + enforce-first-as: true + enforce-multihop: true + local-as: 65299 + shutdown-message: pg_shutdown_msg + local-as-no-prepend: true + local-as-replace-as: true + override-capability: true + peer-as: 65399 + solo-peer: true + peer-group-name: SPINETEST1 + enable-bfd: + config: + enabled: true + check-control-plane-failure: true + bfd-profile: 'bfd_profile' + ebgp-multihop: + config: + enabled: true + multihop-ttl: 22 + auth-password: + config: + encrypted: true + password: 'U2FsdGVkX1+LHXncDf0uAxQrs4CN7H5yDKT5sht6Ga4=' + advertisement-interval: 15 + timers: + config: + keepalive-interval: 77 + hold-time: 78 + connect-retry: 11 + transport: + config: + passive-mode: true + local-address: 5.5.5.5 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + allow-own-as: + config: + as-count: 8 + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + ipv6-unicast: + prefix-limit: + config: + max-prefixes: 20 + prevent-teardown: true + warning-threshold-pct: 40 + restart-time: 60 + allow-own-as: + config: + origin: True + config: + afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + enabled: true + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + config: + afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + prefix-list: + config: + import-policy: p1 + export-policy: p2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + value: + openconfig-network-instance:neighbors: + neighbor: + - auth-password: + config: + password: pw123 + encrypted: False + neighbor-address: Eth1/2 + ebgp-multihop: + config: + enabled: True + multihop-ttl: 1 + transport: + config: + local-address: Ethernet4 + passive-mode: False + config: + neighbor-address: Eth1/2 + description: 'description 1' + dont-negotiate-capability: True + enforce-first-as: True + enforce-multihop: True + override-capability: True + peer-port: 3 + shutdown-message: msg1 + solo-peer: True + local-as: 2 + local-as-no-prepend: True + local-as-replace-as: True + capability-extended-nexthop: True + - neighbor-address: 1.1.1.1 + transport: + config: + passive-mode: False + config: + neighbor-address: 1.1.1.1 + disable-ebgp-connected-route-check: True + ttl-security-hops: 5 + capability-extended-nexthop: True + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=1.1.1.1/config/capability-extended-nexthop" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/peer-groups" + method: "patch" + data: + openconfig-network-instance:peer-groups: + peer-group: + - afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + allow-own-as: + config: + enabled: true + origin: true + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + peer-group-name: SPINE1 + peer-group-name: SPINE1 + timers: + config: + minimum-advertisement-interval: 20 + transport: + config: + passive-mode: false + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - config: + neighbor-address: Eth1/3 + peer-as: 15 + neighbor-address: Eth1/3 + timers: + config: + minimum-advertisement-interval: 10 + transport: + config: + passive-mode: false + - config: + neighbor-address: '1.1.1.1' + neighbor-address: '1.1.1.1' + timers: + config: + minimum-advertisement-interval: 20 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - config: + capability-dynamic: true + neighbor-address: Eth1/1 + enable-bfd: + config: + bfd-profile: "profile 2" + check-control-plane-failure: true + enabled: true + neighbor-address: Eth1/1 + transport: + config: + passive-mode: false + + +overridden_01: + module_args: + config: + - bgp_as: 51 + neighbors: + - neighbor: "Eth1/3" + advertisement_interval: 10 + remote_as: + peer_as: 15 + - neighbor: "1.1.1.1" + ttl_security: 5 + advertisement_interval: 20 + disable_connected_check: true + passive: False + peer_group: + - name: "SPINE1" + address_family: + afis: + - afi: "ipv4" + allowas_in: + origin: true + safi: "unicast" + advertisement_interval: 20 + - bgp_as: 51 + vrf_name: "VrfReg2" + neighbors: + - neighbor: "Eth1/1" + capability: + dynamic: true + bfd: + check_failure: true + enabled: true + profile: "profile 2" + state: overridden + existing_bgp_config: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg3/protocols/protocol=BGP,bgp/bgp/global/config" + response: + code: 200 + value: + openconfig-network-instance:config: + as: 51 + router-id: "10.2.2.4" + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - vrf_name: VrfReg3 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + value: + openconfig-network-instance:peer-groups: + peer-group: + - config: + capability-dynamic: true + capability-extended-nexthop: true + enabled: true + peer-group-name: SPINETEST1 + description: "pg_description" + disable-ebgp-connected-route-check: true + dont-negotiate-capability: true + enforce-first-as: true + enforce-multihop: true + local-as: 65299 + shutdown-message: pg_shutdown_msg + local-as-no-prepend: true + local-as-replace-as: true + override-capability: true + peer-as: 65399 + solo-peer: true + peer-group-name: SPINETEST1 + enable-bfd: + config: + enabled: true + check-control-plane-failure: true + bfd-profile: 'bfd_profile' + ebgp-multihop: + config: + enabled: true + multihop-ttl: 22 + auth-password: + config: + encrypted: true + password: 'U2FsdGVkX1+LHXncDf0uAxQrs4CN7H5yDKT5sht6Ga4=' + advertisement-interval: 15 + timers: + config: + keepalive-interval: 77 + hold-time: 78 + connect-retry: 11 + transport: + config: + passive-mode: true + local-address: 5.5.5.5 + afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + allow-own-as: + config: + as-count: 8 + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + - afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + ipv6-unicast: + prefix-limit: + config: + max-prefixes: 20 + prevent-teardown: true + warning-threshold-pct: 40 + restart-time: 60 + allow-own-as: + config: + origin: True + config: + afi-safi-name: openconfig-bgp-types:IPV6_UNICAST + enabled: true + - afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + config: + afi-safi-name: openconfig-bgp-types:L2VPN_EVPN + prefix-list: + config: + import-policy: p1 + export-policy: p2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg3/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg3/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + value: + openconfig-network-instance:neighbors: + neighbor: + - auth-password: + config: + password: pw123 + encrypted: False + neighbor-address: Eth1/2 + ebgp-multihop: + config: + enabled: True + multihop-ttl: 1 + transport: + config: + local-address: Ethernet4 + passive-mode: False + config: + neighbor-address: Eth1/2 + description: 'description 1' + dont-negotiate-capability: True + enforce-first-as: True + enforce-multihop: True + override-capability: True + peer-port: 3 + shutdown-message: msg1 + solo-peer: True + local-as: 2 + local-as-no-prepend: True + local-as-replace-as: True + capability-extended-nexthop: True + - neighbor-address: 1.1.1.1 + transport: + config: + passive-mode: False + config: + neighbor-address: 1.1.1.1 + disable-ebgp-connected-route-check: True + ttl-security-hops: 5 + capability-extended-nexthop: True + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" + response: + code: 200 + value: + openconfig-network-instance:neighbors: + neighbor: + - auth-password: + config: + password: pw123 + encrypted: False + neighbor-address: Eth1/2 + ebgp-multihop: + config: + enabled: True + multihop-ttl: 1 + transport: + config: + local-address: Ethernet4 + passive-mode: False + config: + neighbor-address: Eth1/2 + description: 'description 1' + dont-negotiate-capability: True + enforce-first-as: True + enforce-multihop: True + override-capability: True + peer-port: 3 + shutdown-message: msg1 + solo-peer: True + local-as: 2 + local-as-no-prepend: True + local-as-replace-as: True + capability-extended-nexthop: True + - neighbor-address: 1.1.1.1 + transport: + config: + passive-mode: False + config: + neighbor-address: 1.1.1.1 + disable-ebgp-connected-route-check: True + ttl-security-hops: 5 + capability-extended-nexthop: True + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/peer-groups" + response: + code: 200 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=1.1.1.1/config/capability-extended-nexthop" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=Eth1%2f2/" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=BGP,bgp/bgp/peer-groups/peer-group=SPINETEST1" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg3/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=Eth1%2f2/" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg3/protocols/protocol=BGP,bgp/bgp/neighbors/neighbor=1.1.1.1/" + method: "delete" + data: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/peer-groups" + method: "patch" + data: + openconfig-network-instance:peer-groups: + peer-group: + - afi-safis: + afi-safi: + - afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + allow-own-as: + config: + enabled: true + origin: true + config: + afi-safi-name: openconfig-bgp-types:IPV4_UNICAST + config: + peer-group-name: SPINE1 + peer-group-name: SPINE1 + timers: + config: + minimum-advertisement-interval: 20 + transport: + config: + passive-mode: false + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - config: + neighbor-address: Eth1/3 + peer-as: 15 + neighbor-address: Eth1/3 + timers: + config: + minimum-advertisement-interval: 10 + transport: + config: + passive-mode: false + - config: + neighbor-address: '1.1.1.1' + neighbor-address: '1.1.1.1' + timers: + config: + minimum-advertisement-interval: 20 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=BGP,bgp/bgp/neighbors" + method: "patch" + data: + openconfig-network-instance:neighbors: + neighbor: + - config: + capability-dynamic: true + neighbor-address: Eth1/1 + enable-bfd: + config: + bfd-profile: "profile 2" + check-control-plane-failure: true + enabled: true + neighbor-address: Eth1/1 + transport: + config: + passive-mode: false diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_l3_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_l3_interfaces.yaml index fea3cea89..85b8dff79 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_l3_interfaces.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_l3_interfaces.yaml @@ -10,9 +10,12 @@ merged_01: secondary: True ipv6: enabled: true + autoconf: true + dad: ENABLE addresses: + - address: 84::/64 + eui64: true - address: 83::1/16 - - address: 84::1/16 - name: Vlan11 ipv4: addresses: @@ -21,7 +24,10 @@ merged_01: secondary: True ipv6: enabled: true + dad: DISABLE_IPV6_ON_FAILURE addresses: + - address: 70::/64 + eui64: true - address: 73::1/16 - name: Vlan12 ipv4: @@ -56,24 +62,35 @@ merged_01: ip: 84.1.1.1 prefix-length: 16.0 secondary: True + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + enabled: True + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_autoconfig: True + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_dad: ENABLE - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses" method: "patch" data: openconfig-if-ip:addresses: address: - - ip: 83::1 + - ip: "84::" openconfig-if-ip:config: - ip: 83::1 - prefix-length: 16.0 - - ip: 84::1 + ip: "84::" + prefix-length: 64.0 + openconfig-interfaces-private:eui64: true + - ip: "83::1" openconfig-if-ip:config: - ip: 84::1 + ip: "83::1" prefix-length: 16.0 - - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" - method: "patch" - data: - config: - enabled: True - path: "data/openconfig-interfaces:interfaces/interface=Vlan11/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/addresses" method: "patch" data: @@ -98,6 +115,11 @@ merged_01: data: openconfig-if-ip:addresses: address: + - ip: "70::" + openconfig-if-ip:config: + ip: "70::" + prefix-length: 64.0 + openconfig-interfaces-private:eui64: true - ip: 73::1 openconfig-if-ip:config: ip: 73::1 @@ -107,6 +129,11 @@ merged_01: data: config: enabled: True + - path: "data/openconfig-interfaces:interfaces/interface=Vlan11/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_dad: DISABLE_IPV6_ON_FAILURE - path: "data/openconfig-interfaces:interfaces/interface=Vlan12/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway" method: "patch" data: @@ -141,6 +168,8 @@ deleted_01: ip: 73::1 prefix-length: 64 enabled: True + ipv6_dad: ENABLE + ipv6_autoconfig: True - name: Vlan99 openconfig-vlan:routed-vlan: openconfig-if-ip:ipv4: @@ -182,6 +211,8 @@ deleted_02: ipv6: addresses: - address: 84::1/64 + - address: 85::/64 + eui64: True - name: Eth1/2 - name: Vlan99 ipv4: @@ -189,6 +220,9 @@ deleted_02: - address: 74.1.1.1/8 secondary: True ipv6: + enabled: true + autoconf: true + dad: DISABLE_IPV6_ON_FAILURE addresses: - address: 73::1/64 - name: Vlan88 @@ -224,6 +258,10 @@ deleted_02: - config: ip: 84::1 prefix-length: 64 + - config: + ip: "85::" + prefix-length: 64 + openconfig-interfaces-private:eui64: True config: enabled: True - name: Eth1/2 @@ -254,6 +292,8 @@ deleted_02: prefix-length: 64 config: enabled: True + ipv6_autoconfig: True + ipv6_dad: ENABLE - name: Vlan99 openconfig-vlan:routed-vlan: openconfig-if-ip:ipv4: @@ -275,6 +315,8 @@ deleted_02: prefix-length: 64 config: enabled: True + ipv6_autoconfig: true + ipv6_dad: DISABLE_IPV6_ON_FAILURE - name: Vlan88 openconfig-vlan:routed-vlan: openconfig-if-ip:ipv4: @@ -288,6 +330,9 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=84::1" method: "delete" data: + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=85::" + method: "delete" + data: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses" method: "delete" data: @@ -297,6 +342,12 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config/enabled" method: "delete" data: + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config/ipv6_autoconfig" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config/ipv6_dad" + method: "delete" + data: - path: "data/openconfig-interfaces:interfaces/interface=Vlan88/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway=11.12.13.14%2f12" method: "delete" data: @@ -306,6 +357,15 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Vlan99/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/addresses/address=73::1" method: "delete" data: + - path: "data/openconfig-interfaces:interfaces/interface=Vlan99/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config/enabled" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces/interface=Vlan99/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config/ipv6_autoconfig" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces/interface=Vlan99/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config/ipv6_dad" + method: "delete" + data: replaced_01: module_args: @@ -317,6 +377,8 @@ replaced_01: - 11.12.13.14/12 ipv6: enabled: true + autoconf: true + dad: DISABLE_IPV6_ON_FAILURE - name: Eth1/1 ipv4: addresses: @@ -325,8 +387,13 @@ replaced_01: secondary: True ipv6: addresses: + - address: 33::/64 + eui64: True - address: 31::1/64 - address: 32::1/64 + enabled: true + autoconf: true + dad: ENABLE existing_l3_interfaces_config: - path: "data/openconfig-interfaces:interfaces/interface" response: @@ -421,6 +488,11 @@ replaced_01: data: openconfig-if-ip:addresses: address: + - ip: "33::" + openconfig-if-ip:config: + ip: "33::" + prefix-length: 64.0 + openconfig-interfaces-private:eui64: true - ip: 31::1 openconfig-if-ip:config: ip: 31::1 @@ -429,6 +501,21 @@ replaced_01: openconfig-if-ip:config: ip: 32::1 prefix-length: 64.0 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + enabled: True + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_autoconfig: True + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_dad: ENABLE - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway" method: "patch" data: @@ -439,6 +526,16 @@ replaced_01: data: config: enabled: True + - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_autoconfig: True + - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_dad: DISABLE_IPV6_ON_FAILURE overridden_01: module_args: @@ -450,6 +547,8 @@ overridden_01: - 11.12.13.14/12 ipv6: enabled: true + autoconf: True + dad: DISABLE_IPV6_ON_FAILURE - name: Eth1/1 ipv4: addresses: @@ -458,6 +557,8 @@ overridden_01: secondary: True ipv6: addresses: + - address: 33::/64 + eui64: True - address: 31::1/64 - address: 32::1/64 existing_l3_interfaces_config: @@ -566,6 +667,11 @@ overridden_01: data: openconfig-if-ip:addresses: address: + - ip: "33::" + openconfig-if-ip:config: + ip: "33::" + prefix-length: 64.0 + openconfig-interfaces-private:eui64: true - ip: 31::1 openconfig-if-ip:config: ip: 31::1 @@ -574,6 +680,11 @@ overridden_01: openconfig-if-ip:config: ip: 32::1 prefix-length: 64.0 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_autoconfig: False - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway" method: "patch" data: @@ -584,3 +695,13 @@ overridden_01: data: config: enabled: True + - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_autoconfig: True + - path: "data/openconfig-interfaces:interfaces/interface=Vlan13/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/config" + method: "patch" + data: + config: + ipv6_dad: DISABLE_IPV6_ON_FAILURE diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml index 8ebc1623a..d8415baf9 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml @@ -7,6 +7,9 @@ merged_01: interfaces: - member: Eth1/11 - member: Eth1/12 + ethernet_segment: + esi_type: auto_lacp + df_preference: 2222 - name: PortChannel20 members: interfaces: @@ -66,6 +69,21 @@ merged_01: method: "patch" data: openconfig-if-aggregate:aggregate-id: PortChannel20 + - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments" + method: "patch" + data: + openconfig-network-instance:ethernet-segments: + ethernet-segment: + - name: PortChannel10 + config: + esi-type: TYPE_1_LACP_BASED + esi: AUTO + interface: PortChannel10 + name: PortChannel10 + df-election: + config: + preference: 2222 + deleted_01: module_args: state: deleted @@ -101,6 +119,8 @@ deleted_02: interfaces: - member: Eth1/12 - member: Eth1/14 + ethernet_segment: + esi_type: auto_system_mac - name: PortChannel20 members: interfaces: @@ -146,6 +166,13 @@ deleted_02: name: PortChannel40 - ifname: Eth1/42 name: PortChannel40 + EVPN_ETHERNET_SEGMENT: + EVPN_ETHERNET_SEGMENT_LIST: + - name: PortChannel10 + ifname: PortChannel10 + esi_type: TYPE_3_MAC_BASED + esi: AUTO + df_pref: 3333 expected_config_requests: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" @@ -162,6 +189,9 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=PortChannel40" method: "delete" data: + - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" + method: "delete" + data: deleted_03: module_args: @@ -202,3 +232,143 @@ deleted_03: - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL_MEMBER/PORTCHANNEL_MEMBER_LIST" method: "delete" data: + +replaced_01: + module_args: + state: replaced + config: + - name: PortChannel10 + members: + interfaces: + - member: Eth1/12 + - member: Eth1/14 + ethernet_segment: + esi_type: auto_system_mac + - name: PortChannel20 + members: + interfaces: + - member: Eth1/23 + existing_lag_interfaces_config: + - path: "data/sonic-portchannel:sonic-portchannel" + response: + code: 200 + value: + sonic-portchannel:sonic-portchannel: + PORTCHANNEL: + PORTCHANNEL_LIST: + - name: PortChannel10 + - name: PortChannel30 + PORTCHANNEL_MEMBER: + PORTCHANNEL_MEMBER_LIST: + - ifname: Eth1/11 + name: PortChannel10 + - ifname: Eth1/31 + name: PortChannel30 + expected_config_requests: + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f11/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces" + method: "patch" + data: + openconfig-interfaces:interfaces: + interface: + - name: PortChannel20 + config: + name: PortChannel20 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "patch" + data: + openconfig-if-aggregate:aggregate-id: PortChannel10 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F14/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "patch" + data: + openconfig-if-aggregate:aggregate-id: PortChannel10 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F23/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "patch" + data: + openconfig-if-aggregate:aggregate-id: PortChannel20 + - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments" + method: "patch" + data: + openconfig-network-instance:ethernet-segments: + ethernet-segment: + - name: PortChannel10 + config: + esi-type: TYPE_3_MAC_BASED + esi: AUTO + interface: PortChannel10 + name: PortChannel10 + +overridden_01: + module_args: + state: overridden + config: + - name: PortChannel10 + members: + interfaces: + - member: Eth1/12 + - member: Eth1/14 + ethernet_segment: + esi_type: auto_lacp + - name: PortChannel20 + members: + interfaces: + - member: Eth1/23 + existing_lag_interfaces_config: + - path: "data/sonic-portchannel:sonic-portchannel" + response: + code: 200 + value: + sonic-portchannel:sonic-portchannel: + PORTCHANNEL: + PORTCHANNEL_LIST: + - name: PortChannel10 + - name: PortChannel30 + PORTCHANNEL_MEMBER: + PORTCHANNEL_MEMBER_LIST: + - ifname: Eth1/14 + name: PortChannel10 + - ifname: Eth1/31 + name: PortChannel30 + EVPN_ETHERNET_SEGMENT: + EVPN_ETHERNET_SEGMENT_LIST: + - name: PortChannel10 + ifname: PortChannel10 + esi_type: TYPE_3_MAC_BASED + esi: AUTO + df_pref: 3333 + expected_config_requests: + - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces/interface=PortChannel30" + method: "delete" + data: + - path: "data/openconfig-interfaces:interfaces" + method: "patch" + data: + openconfig-interfaces:interfaces: + interface: + - name: PortChannel20 + config: + name: PortChannel20 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "patch" + data: + openconfig-if-aggregate:aggregate-id: PortChannel10 + - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F23/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" + method: "patch" + data: + openconfig-if-aggregate:aggregate-id: PortChannel20 + - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments" + method: "patch" + data: + openconfig-network-instance:ethernet-segments: + ethernet-segment: + - name: PortChannel10 + config: + esi-type: TYPE_1_LACP_BASED + esi: AUTO + interface: PortChannel10 + name: PortChannel10 diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_ldap.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_ldap.yaml new file mode 100644 index 000000000..eb9ecfcae --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_ldap.yaml @@ -0,0 +1,744 @@ +--- +merged_01: + module_args: + state: merged + config: + - name: "global" + port: 390 + version: 2 + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + priority: 10 + port: 1550 + ssl: start_tls + binddn: "CN=example.com" + map: + default_attribute: + - from: "attr1" + to: "attr2" + - from: "attr3" + to: "attr4" + objectclass: + - from: "attr1" + to: "attr3" + - name: "nss" + nss_base_netgroup: "group1" + idle_timelimit: 25 + timelimit: 15 + scope: "sub" + nss_base_sudoers: "sudo1" + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP" + method: "patch" + data: + openconfig-system:server-group: + - config: + name: 'LDAP' + name: 'LDAP' + openconfig-aaa-ldap-ext:ldap: + config: + bind-dn: 'CN=example.com' + port: 390 + version: 2 + maps: + map: + - config: + to: 'attr2' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + to: 'attr4' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + to: 'attr3' + from: 'attr1' + name: 'OBJECTCLASS' + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + - address: 244.178.44.111 + config: + address: 244.178.44.111 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + ssl: 'START_TLS' + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_NSS" + method: "patch" + data: + openconfig-system:server-group: + - config: + name: 'LDAP_NSS' + name: 'LDAP_NSS' + openconfig-aaa-ldap-ext:ldap: + config: + idle-time-limit: 25 + nss-base-netgroup: 'group1' + nss-base-sudoers: 'sudo1' + scope: 'SUB' + search-time-limit: 15 + +merged_02: + module_args: + state: merged + config: + - name: "global" + nss_base_passwd: password + pam_login_attribute: "loginattrstring" + nss_skipmembers: false + vrf: 'VrfReg1' + - name: "sudo" + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: "base_name" + sudoers_search_filter: "filter1" + timelimit: 10 + version: 3 + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + server-group: + - openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + from: 'attr1' + to: 'attr2' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr3' + to: 'attr4' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr1' + to: 'attr3' + name: 'OBJECTCLASS' + from: 'attr1' + name: 'OBJECTCLASS' + config: + bind-dn: 'CN=example.com' + bind-time-limit: 10 + idle-time-limit: 0 + search-time-limit: 0 + port: 390 + retransmit-attempts: 0 + scope: 'SUB' + ssl: 'OFF' + version: 2 + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + priority: 1 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 1 + retransmit-attempts: 0 + ssl: 'OFF' + use-type: 'ALL' + - address: 244.178.44.111 + config: + address: 244.178.44.111 + priority: 10 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + retransmit-attempts: 0 + ssl: 'START_TLS' + use-type: 'ALL' + name: "LDAP" + config: + name: "LDAP" + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP" + method: "patch" + data: + openconfig-system:server-group: + - config: + name: 'LDAP' + name: 'LDAP' + openconfig-aaa-ldap-ext:ldap: + config: + nss-base-passwd: 'password' + nss-skipmembers: False + pam-login-attribute: 'loginattrstring' + vrf-name: 'VrfReg1' + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_SUDO" + method: "patch" + data: + openconfig-system:server-group: + - config: + name: 'LDAP_SUDO' + name: 'LDAP_SUDO' + openconfig-aaa-ldap-ext:ldap: + config: + base: 'base_name' + bind-pw: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + search-time-limit: 10 + sudoers-search-filter: 'filter1' + version: 3 + +deleted_01: + module_args: + config: + - name: global + vrf: 'VrfReg2' + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - 'secadmin' + nss_base_passwd: 'password2' + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + - name: pam + bindpw: + pwd: "U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=" + encrypted: true + base: admin + binddn: "CN=example.com" + retry: 3 + scope: one + state: deleted + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + server-group: + - name: 'LDAP' + config: + name: 'LDAP' + openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + from: 'attr1' + to: 'attr2' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr3' + to: 'attr4' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr1' + to: 'attr3' + name: 'OBJECTCLASS' + from: 'attr1' + name: 'OBJECTCLASS' + - config: + from: 'user1' + to: 'secadmin,netadmin' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + config: + bind-dn: 'CN=example.com' + bind-time-limit: 10 + idle-time-limit: 0 + search-time-limit: 0 + port: 390 + retransmit-attempts: 0 + scope: 'SUB' + vrf-name: 'VrfReg2' + ssl: 'OFF' + version: 2 + nss-base-passwd: 'password2' + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + priority: 1 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 1 + retransmit-attempts: 0 + ssl: 'OFF' + use-type: 'ALL' + - address: 244.178.44.111 + config: + address: 244.178.44.111 + priority: 10 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + retransmit-attempts: 0 + ssl: 'START_TLS' + use-type: 'ALL' + - name: 'LDAP_PAM' + config: + name: 'LDAP_PAM' + openconfig-aaa-ldap-ext:ldap: + config: + scope: 'ONE' + bind-dn: 'CN=example.com' + retransmit-attempts: 3 + bind-pw: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + version: 3 + base: admin + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/nss-base-passwd" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/vrf-name" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE,user1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=244.178.44.111" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=89.0.142.86" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_PAM" + method: "delete" + +deleted_02: + module_args: + config: + state: deleted + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + server-group: + - openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + from: 'attr1' + to: 'attr2' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr3' + to: 'attr4' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr1' + to: 'attr3' + name: 'OBJECTCLASS' + from: 'attr1' + name: 'OBJECTCLASS' + - config: + from: 'user1' + to: 'secadmin,netadmin' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + config: + bind-dn: 'CN=example.com' + bind-time-limit: 10 + idle-time-limit: 0 + search-time-limit: 0 + port: 390 + retransmit-attempts: 0 + scope: 'SUB' + vrf-name: 'VrfReg2' + ssl: 'OFF' + version: 2 + nss-base-passwd: 'password2' + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + priority: 1 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 1 + retransmit-attempts: 0 + ssl: 'OFF' + use-type: 'ALL' + - address: 244.178.44.111 + config: + address: 244.178.44.111 + priority: 10 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + retransmit-attempts: 0 + ssl: 'START_TLS' + use-type: 'ALL' + name: "LDAP" + config: + name: "LDAP" + - name: 'LDAP_PAM' + config: + name: 'LDAP_PAM' + openconfig-aaa-ldap-ext:ldap: + config: + scope: 'ONE' + bind-dn: 'CN=example.com' + retransmit-attempts: 3 + bind-pw: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + version: 3 + base: admin + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_PAM" + method: "delete" + +replaced_01: + module_args: + state: replaced + config: + - name: 'nss' + nss_base_netgroup: 'group1' + idle_timelimit: 25 + timelimit: 15 + scope: 'sub' + nss_base_sudoers: 'sudo1' + nss_base_passwd: 'password2' + nss_base_group: 'group1' + nss_base_shadow: 'password2' + - name: 'global' + vrf: 'VrfReg2' + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - 'secadmin' + nss_base_passwd: 'password2' + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + server-group: + - openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + from: 'attr1' + to: 'attr2' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr3' + to: 'attr4' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr1' + to: 'attr3' + name: 'OBJECTCLASS' + from: 'attr1' + name: 'OBJECTCLASS' + - config: + from: 'user1' + to: 'secadmin,netadmin' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + config: + bind-dn: 'CN=example.com' + bind-time-limit: 10 + idle-time-limit: 0 + search-time-limit: 0 + port: 390 + retransmit-attempts: 0 + scope: 'SUB' + vrf-name: 'VrfReg2' + ssl: 'OFF' + version: 2 + nss-base-passwd: 'password2' + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + priority: 1 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 1 + retransmit-attempts: 0 + ssl: 'OFF' + use-type: 'ALL' + - address: 244.178.44.111 + config: + address: 244.178.44.111 + priority: 10 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + retransmit-attempts: 0 + ssl: 'START_TLS' + use-type: 'ALL' + name: "LDAP" + config: + name: "LDAP" + - name: 'LDAP_PAM' + config: + name: 'LDAP_PAM' + openconfig-aaa-ldap-ext:ldap: + config: + scope: 'ONE' + bind-dn: 'CN=example.com' + retransmit-attempts: 3 + bind-pw: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + version: 3 + base: admin + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/bind-dn" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/bind-time-limit" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/port" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/scope" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/ssl" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/version" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=DEFAULT_ATTRIBUTE_VALUE,attr1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=DEFAULT_ATTRIBUTE_VALUE,attr3" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE,user1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=OBJECTCLASS,attr1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=244.178.44.111" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=89.0.142.86" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP" + method: "patch" + data: + openconfig-system:server-group: + - name: "LDAP" + config: + name: "LDAP" + openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + to: 'secadmin' + from: 'user1' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_NSS" + method: "patch" + data: + openconfig-system:server-group: + - name: "LDAP_NSS" + config: + name: "LDAP_NSS" + openconfig-aaa-ldap-ext:ldap: + config: + idle-time-limit: 25 + nss-base-group: 'group1' + nss-base-netgroup: 'group1' + nss-base-sudoers: 'sudo1' + scope: 'SUB' + nss-base-shadow: 'password2' + nss-base-passwd: 'password2' + search-time-limit: 15 + +overridden_01: + module_args: + state: overridden + config: + - name: 'nss' + nss_base_netgroup: 'group1' + idle_timelimit: 25 + timelimit: 15 + scope: 'sub' + nss_base_sudoers: 'sudo1' + nss_base_passwd: 'password2' + nss_base_group: 'group1' + nss_base_shadow: 'password2' + - name: 'global' + vrf: 'VrfReg2' + map: + map_remote_groups_to_sonic_roles: + - remote_group: "user1" + sonic_roles: + - 'secadmin' + nss_base_passwd: 'password2' + servers: + - address: 89.0.142.86 + - address: 244.178.44.111 + existing_ldap_config: + - path: "data/openconfig-system:system/aaa/server-groups" + response: + code: 200 + value: + openconfig-system:server-groups: + server-group: + - openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + from: 'attr1' + to: 'attr2' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr1' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr3' + to: 'attr4' + name: 'DEFAULT_ATTRIBUTE_VALUE' + from: 'attr3' + name: 'DEFAULT_ATTRIBUTE_VALUE' + - config: + from: 'attr1' + to: 'attr3' + name: 'OBJECTCLASS' + from: 'attr1' + name: 'OBJECTCLASS' + - config: + from: 'user1' + to: 'secadmin,netadmin' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + config: + bind-dn: 'CN=example.com' + bind-time-limit: 10 + idle-time-limit: 0 + search-time-limit: 0 + port: 390 + retransmit-attempts: 0 + scope: 'SUB' + vrf-name: 'VrfReg2' + ssl: 'OFF' + version: 2 + nss-base-passwd: 'password2' + servers: + server: + - address: 89.0.142.86 + config: + address: 89.0.142.86 + priority: 1 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 1 + retransmit-attempts: 0 + ssl: 'OFF' + use-type: 'ALL' + - address: 244.178.44.111 + config: + address: 244.178.44.111 + priority: 10 + openconfig-aaa-ldap-ext:ldap: + config: + priority: 10 + port: 1550 + retransmit-attempts: 0 + ssl: 'START_TLS' + use-type: 'ALL' + name: "LDAP" + config: + name: "LDAP" + - name: 'LDAP_PAM' + config: + name: 'LDAP_PAM' + openconfig-aaa-ldap-ext:ldap: + config: + scope: 'ONE' + bind-dn: 'CN=example.com' + retransmit-attempts: 3 + bind-pw: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + version: 3 + base: admin + expected_config_requests: + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/bind-dn" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/bind-time-limit" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/port" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/scope" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/ssl" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/config/version" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=DEFAULT_ATTRIBUTE_VALUE,attr1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=DEFAULT_ATTRIBUTE_VALUE,attr3" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE,user1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/openconfig-aaa-ldap-ext:ldap/maps/map=OBJECTCLASS,attr1" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=244.178.44.111" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP/servers/server=89.0.142.86" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_PAM" + method: "delete" + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP" + method: "patch" + data: + openconfig-system:server-group: + - name: "LDAP" + config: + name: "LDAP" + openconfig-aaa-ldap-ext:ldap: + maps: + map: + - config: + to: 'secadmin' + from: 'user1' + name: 'CUSTOM_SONIC_ROLES_ATTRIBUTE_VALUE' + - path: "data/openconfig-system:system/aaa/server-groups/server-group=LDAP_NSS" + method: "patch" + data: + openconfig-system:server-group: + - name: "LDAP_NSS" + config: + name: "LDAP_NSS" + openconfig-aaa-ldap-ext:ldap: + config: + idle-time-limit: 25 + nss-base-group: 'group1' + nss-base-netgroup: 'group1' + nss-base-sudoers: 'sudo1' + scope: 'SUB' + nss-base-shadow: 'password2' + nss-base-passwd: 'password2' + search-time-limit: 15 diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_mgmt_servers.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_mgmt_servers.yaml new file mode 100644 index 000000000..14ec8d477 --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_mgmt_servers.yaml @@ -0,0 +1,348 @@ +merged_01: + module_args: + config: + rest: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + existing_mgmt_servers_config: + - path: '/data/openconfig-system:system/rest-server/config' + response: + code: 200 + - path: '/data/openconfig-system:system/telemetry-server/config' + response: + code: 200 + expected_config_requests: + - path: '/data/openconfig-system:system' + method: 'patch' + data: + openconfig-system:system: + rest-server: + config: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + openconfig-system-mgmt-servers:disable: False + vrf: mgmt + telemetry-server: + config: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + +replaced_01: + module_args: + config: + rest: + log_level: 24 + read_timeout: 130 + req_limit: 500 + telemetry: + jwt_valid: 800 + log_level: 25 + port: 9876 + security_profile: profile1 + state: replaced + existing_mgmt_servers_config: + - path: '/data/openconfig-system:system/rest-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 240 + client_auth: none + log_level: 12 + port: 443 + read_timeout: 65 + req_limit: 150 + security_profile: profile2 + openconfig-system-mgmt-servers:disable: False + vrf: mgmt + - path: '/data/openconfig-system:system/telemetry-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 90 + client_auth: jwt,password + jwt_refresh: 160 + jwt_valid: 600 + log_level: 20 + port: 5678 + security_profile: profile1 + vrf: mgmt + expected_config_requests: + - path: '/data/openconfig-system:system/rest-server' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server' + method: 'delete' + data: + - path: '/data/openconfig-system:system' + method: 'patch' + data: + openconfig-system:system: + rest-server: + config: + api_timeout: 900 + client_auth: password,jwt + log_level: 24 + port: 443 + read_timeout: 130 + req_limit: 500 + telemetry-server: + config: + api_timeout: 0 + client_auth: password,jwt + jwt_refresh: 900 + jwt_valid: 800 + log_level: 25 + port: 9876 + security_profile: profile1 + +overridden_01: + module_args: + config: + rest: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: False + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + state: overridden + existing_mgmt_servers_config: + - path: '/data/openconfig-system:system/rest-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 900 + client_auth: password,jwt + log_level: 24 + port: 443 + read_timeout: 130 + req_limit: 500 + - path: '/data/openconfig-system:system/telemetry-server/config' + response: + code: 200 + value: + openconfig-system:config: + client_auth: password,jwt + jwt_refresh: 900 + jwt_valid: 800 + log_level: 25 + port: 9876 + security_profile: profile1 + expected_config_requests: + - path: '/data/openconfig-system:system/rest-server' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server' + method: 'delete' + data: + - path: '/data/openconfig-system:system' + method: 'patch' + data: + openconfig-system:system: + rest-server: + config: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 443 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + openconfig-system-mgmt-servers:disable: False + vrf: mgmt + telemetry-server: + config: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + +deleted_01: + module_args: + config: + rest: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 100 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + shutdown: True + vrf: mgmt + telemetry: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + state: deleted + existing_mgmt_servers_config: + - path: '/data/openconfig-system:system/rest-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 120 + client_auth: password,jwt,cert + log_level: 6 + port: 100 + read_timeout: 60 + req_limit: 100 + security_profile: profile1 + openconfig-system-mgmt-servers:disable: True + vrf: mgmt + - path: '/data/openconfig-system:system/telemetry-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 45 + client_auth: jwt,cert + jwt_refresh: 80 + jwt_valid: 300 + log_level: 10 + port: 1234 + security_profile: profile2 + vrf: mgmt + expected_config_requests: + - path: '/data/openconfig-system:system/rest-server/config/api_timeout' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/client_auth' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/log_level' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/port' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/read_timeout' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/req_limit' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/security_profile' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/openconfig-system-mgmt-servers:disable' + method: 'delete' + data: + - path: '/data/openconfig-system:system/rest-server/config/vrf' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/api_timeout' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/client_auth' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/jwt_refresh' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/jwt_valid' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/log_level' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/port' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/security_profile' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server/config/vrf' + method: 'delete' + data: + +deleted_02: + module_args: + config: {} + state: deleted + existing_mgmt_servers_config: + - path: '/data/openconfig-system:system/rest-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 900 + client_auth: password,jwt + log_level: 0 + port: 443 + read_timeout: 15 + req_limit: 100 + security_profile: profile1 + openconfig-system-mgmt-servers:disable: True + vrf: mgmt + - path: '/data/openconfig-system:system/telemetry-server/config' + response: + code: 200 + value: + openconfig-system:config: + api_timeout: 0 + client_auth: password,jwt + jwt_refresh: 900 + jwt_valid: 3600 + log_level: 0 + port: 8080 + security_profile: profile2 + vrf: mgmt + expected_config_requests: + - path: '/data/openconfig-system:system/rest-server' + method: 'delete' + data: + - path: '/data/openconfig-system:system/telemetry-server' + method: 'delete' + data: diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_ospf_area.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_ospf_area.yaml new file mode 100644 index 000000000..8934832b8 --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_ospf_area.yaml @@ -0,0 +1,2265 @@ +--- +merged_keys: + module_args: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + virtual_links: + - router_id: 1.1.1.1 + authentication: # difference in key + key: "U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM=" + key_encrypted: True + - router_id: 1.1.1.2 + authentication: # both different + key: qwerty + key_encrypted: False + - router_id: 1.1.1.3 + authentication: # no difference in settings + key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + key_encrypted: True + - router_id: 1.1.1.4 # both different missing setting + authentication: + key: somepass + - router_id: 1.1.1.5 + message_digest_list: + - key_id: 1 # difference in key + key: "U2FsdGVkX18gVkebG/p0IbbrpVZUtsoq+R36CUYk920=" + key_encrypted: True + - key_id: 2 # both different + key: jgnm39gCepKbe + key_encrypted: False + - key_id: 3 # no difference in settings + key: "U2FsdGVkX1/Ydycn92VSf/FMdj0Dm7P2xWrgQFHVC3I=" + key_encrypted: True + - key_id: 4 # both differeing missing setting + key: g0bNel2 + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - identifier: 0.0.0.1 + config: + identifier: 0.0.0.1 + virtual-links: + virtual-link: + - remote-router-id: 1.1.1.1 + config: + remote-router-id: 1.1.1.1 + openconfig-ospfv2-ext:authentication-key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + openconfig-ospfv2-ext:authentication-key-encrypted: True + - remote-router-id: 1.1.1.2 + config: + remote-router-id: 1.1.1.2 + openconfig-ospfv2-ext:authentication-key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + openconfig-ospfv2-ext:authentication-key-encrypted: True + - remote-router-id: 1.1.1.3 + config: + remote-router-id: 1.1.1.3 + openconfig-ospfv2-ext:authentication-key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + openconfig-ospfv2-ext:authentication-key-encrypted: True + - remote-router-id: 1.1.1.4 + config: + remote-router-id: 1.1.1.4 + openconfig-ospfv2-ext:authentication-key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + openconfig-ospfv2-ext:authentication-key-encrypted: True + - remote-router-id: 1.1.1.5 + config: + remote-router-id: 1.1.1.5 + openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-id: 1 + authentication-md5-key: "U2FsdGVkX1/Ydycn92VSf/FMdj0Dm7P2xWrgQFHVC3I=" + authentication-key-encrypted: True + - authentication-key-id: 2 + config: + authentication-key-id: 2 + authentication-md5-key: "U2FsdGVkX1/Ydycn92VSf/FMdj0Dm7P2xWrgQFHVC3I=" + authentication-key-encrypted: True + - authentication-key-id: 3 + config: + authentication-key-id: 3 + authentication-md5-key: "U2FsdGVkX1/Ydycn92VSf/FMdj0Dm7P2xWrgQFHVC3I=" + authentication-key-encrypted: True + - authentication-key-id: 4 + config: + authentication-key-id: 4 + authentication-md5-key: "U2FsdGVkX1/Ydycn92VSf/FMdj0Dm7P2xWrgQFHVC3I=" + authentication-key-encrypted: True + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + identifier: 0.0.0.1 + identifier: 0.0.0.1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:authentication-key: qwerty + openconfig-ospfv2-ext:authentication-key-encrypted: False + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + - config: + openconfig-ospfv2-ext:authentication-key: somepass + openconfig-ospfv2-ext:authentication-key-encrypted: False + remote-router-id: 1.1.1.4 + remote-router-id: 1.1.1.4 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX18gVkebG/p0IbbrpVZUtsoq+R36CUYk920= + - authentication-key-id: 2 + config: + authentication-key-encrypted: False + authentication-key-id: 2 + authentication-md5-key: jgnm39gCepKbe + - authentication-key-id: 4 + config: + authentication-key-id: 4 + authentication-md5-key: g0bNel2 + authentication-key-encrypted: False + config: + remote-router-id: 1.1.1.5 + remote-router-id: 1.1.1.5 + makes_changes: True + +merged_01_all_settings: + module_args: + state: merged + config: + - area_id: 1 + vrf_name: Vrf1 + - area_id: 2 + vrf_name: Vrf1 + authentication_type: message_digest + default_cost: 3 + stub: + enabled: True + no_summary: True + shortcut: default + - area_id: 3 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + ranges: + - prefix: 1.1.1.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: False + substitute: 2.2.2.2/24 + - prefix: 1.1.1.4/24 + advertise: True + cost: 10 + substitute: 3.3.3.3/24 + - area_id: 4 + vrf_name: Vrf1 + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 5 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + hello_interval: 30 + retransmit_interval: 40 + transmit_delay: 50 + authentication: + auth_type: text + key: "U2FsdGVkX197YJtZ/3Ac6n5kRIG/ZqeU1/wC0cVFyfU=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX1/wbqjMB7Lr+Mm3wY8+lCdaqUmG2rr9Adw=" + key_encrypted: True + - key_id: 2 + key: "U2FsdGVkX18Czj9r8skDrg/wtpwTKKCQ8FXUehpCmHc=" + key_encrypted: True + - area_id: 6 + vrf_name: Vrf2 + shortcut: default + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - vrf_name: Vrf2 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: {} + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: [] + areas: + area: [] + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf2/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: {} + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: [] + areas: + area: [] + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + global: + config: {} + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - src-area: 0.0.0.3 + ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + address-prefix: 1.1.1.2/24 + advertise: True + metric: 4 + - address-prefix: 1.1.1.3/24 + config: + address-prefix: 1.1.1.3/24 + advertise: False + substitue-prefix: 2.2.2.2/24 + - address-prefix: 1.1.1.4/24 + config: + address-prefix: 1.1.1.4/24 + advertise: True + metric: 10 + substitue-prefix: 3.3.3.3/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + areas: + area: + - identifier: 0.0.0.2 + config: + identifier: 0.0.0.2 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DEFAULT + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + - identifier: 0.0.0.3 + config: + identifier: 0.0.0.3 + - identifier: 0.0.0.4 + config: + identifier: 0.0.0.4 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + - identifier: 0.0.0.5 + config: + identifier: 0.0.0.5 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - remote-router-id: 34.7.35.1 + config: + remote-router-id: 34.7.35.1 + - remote-router-id: 34.7.35.2 + config: + remote-router-id: 34.7.35.2 + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: "U2FsdGVkX197YJtZ/3Ac6n5kRIG/ZqeU1/wC0cVFyfU=" + openconfig-ospfv2-ext:authentication-key-encrypted: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX1/wbqjMB7Lr+Mm3wY8+lCdaqUmG2rr9Adw= + authentication-key-encrypted: True + - authentication-key-id: 2 + config: + authentication-key-id: 2 + authentication-md5-key: U2FsdGVkX18Czj9r8skDrg/wtpwTKKCQ8FXUehpCmHc= + authentication-key-encrypted: True + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf2/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - identifier: 0.0.0.6 + config: + identifier: 0.0.0.6 + openconfig-ospfv2-ext:shortcut: DEFAULT + makes_changes: True + +merged_add_minimum: + module_args: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + - area_id: 0.0.0.2 + vrf_name: Vrf1 + networks: + - 1.1.1.1/24 + - area_id: 0.0.0.3 + vrf_name: Vrf1 + ranges: + - prefix: 1.1.1.1/24 + - area_id: 0.0.0.4 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + - area_id: 0.0.0.5 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + message_digest_list: + - key_id: 1 + key: grighr + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: {} + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: [] + areas: + area: [] + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - identifier: 0.0.0.2 + config: + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - identifier: 0.0.0.3 + config: + identifier: 0.0.0.3 + - identifier: 0.0.0.4 + config: + identifier: 0.0.0.4 + - identifier: 0.0.0.5 + config: + identifier: 0.0.0.5 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - src-area: 0.0.0.3 + ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - remote-router-id: 34.7.35.1 + config: + remote-router-id: 34.7.35.1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - remote-router-id: 34.7.35.1 + openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-id: 1 + authentication-md5-key: grighr + authentication-key-encrypted: False + config: + remote-router-id: 34.7.35.1 + makes_changes: True + +merge_changes: + module_args: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: text + default_cost: 5 + filter_list_in: pf2 + filter_list_out: pf1 + networks: + - 1.1.1.5/24 + ranges: + - prefix: 1.1.1.1/24 + advertise: True + cost: 12 + substitute: 11.11.1.1/24 + - prefix: 1.1.1.2/24 + advertise: False + shortcut: enable + stub: + enabled: True + no_summary: False + virtual_links: + - router_id: 1.1.1.1 + enabled: True + dead_interval: 45 + hello_interval: 21 + retransmit_interval: 15 + transmit_delay: 23 + authentication: + auth_type: text + key: "U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "somepass" + - router_id: 1.1.1.2 + dead_interval: 16 + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DISABLE + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 6 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + address-prefix: 1.1.1.2/24 + identifier: 0.0.0.1 + virtual-links: + virtual-link: + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX1+/Bwvi/FO6vbDClI3PPbSxZAgvAB2Mz2k= + - authentication-key-id: 2 + config: + authentication-key-encrypted: True + authentication-key-id: 2 + authentication-md5-key: U2FsdGVkX18gVkebG/p0IbbrpVZUtsoq+R36CUYk920= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:hello-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 10 + openconfig-ospfv2-ext:transmit-delay: 10 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:dead-interval: 34 + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: False + metric: 4 + substitue-prefix: 11.1.1.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + address-prefix: 1.1.1.2/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:shortcut: ENABLE + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + no-summary: False + default-cost: 5 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.5/24 + config: + address-prefix: 1.1.1.5/24 + identifier: 0.0.0.1 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + metric: 12 + substitue-prefix: 11.11.1.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: False + address-prefix: 1.1.1.2/24 + filter-list-in: + config: + name: pf2 + filter-list-out: + config: + name: pf1 + src-area: 0.0.0.1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-id: 1 + authentication-key-encrypted: False + authentication-md5-key: somepass + config: + openconfig-ospfv2-ext:dead-interval: 45 + openconfig-ospfv2-ext:hello-interval: 21 + openconfig-ospfv2-ext:retransmission-interval: 15 + openconfig-ospfv2-ext:transmit-delay: 23 + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:dead-interval: 16 + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + makes_changes: True + +merge_no_changes: + module_args: + state: merged + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: message_digest + default_cost: 6 + filter_list_in: pf1 + filter_list_out: pf2 + networks: + - 1.1.1.1/24 + - 1.1.1.2/24 + ranges: + - prefix: 1.1.1.1/24 + advertise: False + cost: 4 + substitute: 11.1.1.1/24 + - prefix: 1.1.1.2/24 + advertise: True + shortcut: disable + stub: + enabled: True + no_summary: True + virtual_links: + - router_id: 1.1.1.1 + enabled: True + dead_interval: 10 + hello_interval: 10 + retransmit_interval: 10 + transmit_delay: 10 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM=" + key_encrypted: True + - key_id: 2 + key: "U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY=" + key_encrypted: True + - router_id: 1.1.1.2 + dead_interval: 34 + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DISABLE + identifier: 0.0.0.1 + virtual-links: + virtual-link: + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM= + - authentication-key-id: 2 + config: + authentication-key-encrypted: True + authentication-key-id: 2 + authentication-md5-key: U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:hello-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 10 + openconfig-ospfv2-ext:transmit-delay: 10 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:dead-interval: 34 + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 6 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + address-prefix: 1.1.1.2/24 + identifier: 0.0.0.1 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: False + metric: 4 + substitue-prefix: 11.1.1.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + address-prefix: 1.1.1.2/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: [] + makes_changes: False + +delete_areas: + module_args: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + - area_id: 0.0.0.2 + vrf_name: Vrf1 + ranges: + - prefix: 1.1.1.1/24 + advertise: True + shortcut: disable + stub: + enabled: True + no_summary: True + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DISABLE + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 6 + virtual-links: + virtual-link: + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX1+/Bwvi/FO6vbDClI3PPbSxZAgvAB2Mz2k= + - authentication-key-id: 2 + config: + authentication-key-encrypted: True + authentication-key-id: 2 + authentication-md5-key: U2FsdGVkX18gVkebG/p0IbbrpVZUtsoq+R36CUYk920= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:hello-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 10 + openconfig-ospfv2-ext:transmit-delay: 10 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:dead-interval: 34 + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + address-prefix: 1.1.1.2/24 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:shortcut: DISABLE + identifier: 0.0.0.2 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + identifier: 0.0.0.2 + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.3 + identifier: 0.0.0.3 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: False + metric: 4 + substitue-prefix: 11.1.1.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + address-prefix: 1.1.1.2/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + address-prefix: 1.1.1.1/24 + src-area: 0.0.0.2 + - ranges: + range: + - address-prefix: 1.1.4.6/24 + config: + metric: 14 + address-prefix: 1.1.4.6/24 + src-area: 0.0.0.3 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/config/openconfig-ospfv2-ext:authentication-type + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/config/openconfig-ospfv2-ext:shortcut + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/openconfig-ospfv2-ext:stub/config/default-cost + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1/openconfig-ospfv2-ext:networks/network + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/config/openconfig-ospfv2-ext:shortcut + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.2/ranges/range + method: delete + makes_changes: True + +clear_subsections: + module_args: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + ranges: [] + - area_id: 0.0.0.2 + vrf_name: Vrf1 + networks: [] + - area_id: 0.0.0.3 + vrf_name: Vrf1 + virtual_links: [] + - area_id: 4 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + authentication: {} + - router_id: 34.7.35.2 + message_digest_list: [] + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.1 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - config: + identifier: 0.0.0.4 + identifier: 0.0.0.4 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18tAKGLRRym9KllJIgcH2GwPBbqIudWQx8= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX19ZMtLT7w1z361caGhGDjdP9L+rUF14++Q= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX1/+nLJqK3UQOXplD93/3KsFe6qpCMR2PDo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: False + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + src-area: 0.0.0.1 + - filter-list-in: + config: + name: pf1 + src-area: 0.0.0.3 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:networks/network + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:authentication-type + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:authentication-key + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:authentication-key-encrypted + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links/virtual-link=34.7.35.2/openconfig-ospfv2-ext:md-authentications/md-authentication + method: delete + makes_changes: True + +clear_everything: + module_args: + state: deleted + config: [] + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX19ZMtLT7w1z361caGhGDjdP9L+rUF14++Q= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX1/+nLJqK3UQOXplD93/3KsFe6qpCMR2PDo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - config: + identifier: 0.0.0.4 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX19ZMtLT7w1z361caGhGDjdP9L+rUF14++Q= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX1/+nLJqK3UQOXplD93/3KsFe6qpCMR2PDo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + identifier: 0.0.0.4 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: False + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + src-area: 0.0.0.1 + - ranges: + range: + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: False + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + src-area: 0.0.0.4 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.1 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:networks/network + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.4/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:networks/network + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4 + method: delete + makes_changes: True + +delete_attributes: + module_args: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + ranges: + - prefix: 1.1.1.1/24 + - prefix: 1.1.1.2/24 + cost: 4 + - prefix: 1.1.1.3/24 + substitute: 2.2.2.2/24 + - prefix: 1.1.1.4/24 + advertise: True + - area_id: 0.0.0.2 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + - router_id: 34.7.35.2 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + authentication: + key: "U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM=" + key_encrypted: True + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + stub: + enabled: True + no_summary: True + - area_id: 5 + vrf_name: Vrf1 + default_cost: 5 + stub: + enabled: True + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + identifier: 0.0.0.1 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX19ZMtLT7w1z361caGhGDjdP9L+rUF14++Q= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX1/+nLJqK3UQOXplD93/3KsFe6qpCMR2PDo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX1+ewVE5+npqP99NPAF0gHU3TZ5lLx/EqOM= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.4 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + identifier: 0.0.0.4 + - config: + identifier: 0.0.0.5 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: False + default-cost: 5 + identifier: 0.0.0.5 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + metric: 13 + substitue-prefix: 11.2.5.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: True + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + - address-prefix: 1.1.1.4/24 + config: + advertise: True + metric: 34 + substitue-prefix: 3.3.3.3/24 + address-prefix: 1.1.1.4/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range=1.1.1.1%2F24 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range=1.1.1.2%2F24/config/metric + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range=1.1.1.3%2F24/config/substitue-prefix + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range=1.1.1.4%2F24/config/advertise + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/filter-list-in + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/filter-list-out + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:networks/network=1.1.1.1%2F24 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/openconfig-ospfv2-ext:networks/network=3.5.1.5%2F23 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.2/config/openconfig-ospfv2-ext:authentication-type + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:hello-interval + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:transmit-delay + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.1/config/openconfig-ospfv2-ext:authentication-type + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/openconfig-ospfv2-ext:md-authentications/md-authentication=1 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/config/openconfig-ospfv2-ext:enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/config/openconfig-ospfv2-ext:dead-interval + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/config/openconfig-ospfv2-ext:retransmission-interval + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/config/openconfig-ospfv2-ext:authentication-key + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link=34.7.35.2/config/openconfig-ospfv2-ext:authentication-key-encrypted + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/config/openconfig-ospfv2-ext:shortcut + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/openconfig-ospfv2-ext:stub/config/default-cost + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/openconfig-ospfv2-ext:stub/config/enable + method: delete + makes_changes: True + +no_deletes: + module_args: + state: deleted + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: text + default_cost: 15 + filter_list_in: pf2 + filter_list_out: pf1 + networks: + - 1.1.1.5/24 + - 3.5.1.6/24 + ranges: + - prefix: 1.1.2.2/24 + advertise: False + cost: 4 + substitute: 11.1.1.1/24 + - prefix: 1.1.4.2/24 + advertise: True + shortcut: enable + stub: + enabled: False + no_summary: False + virtual_links: + - router_id: 1.1.1.4 + enabled: True + dead_interval: 10 + hello_interval: 10 + retransmit_interval: 10 + transmit_delay: 10 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM=" + key_encrypted: True + - key_id: 2 + key: "U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY=" + key_encrypted: True + - router_id: 1.1.1.2 + dead_interval: 57 + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DISABLE + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 6 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + address-prefix: 1.1.1.2/24 + identifier: 0.0.0.1 + virtual-links: + virtual-link: + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX1//fyBCsQYQI4q743L8Rf1Q1qUOEc75lNM= + - authentication-key-id: 2 + config: + authentication-key-encrypted: True + authentication-key-id: 2 + authentication-md5-key: U2FsdGVkX18tvS+HyOt1zIbx9P8I9NMguQ17NZGd9ZY= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:hello-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 10 + openconfig-ospfv2-ext:transmit-delay: 10 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 1.1.1.1 + remote-router-id: 1.1.1.1 + - config: + openconfig-ospfv2-ext:dead-interval: 34 + remote-router-id: 1.1.1.2 + remote-router-id: 1.1.1.2 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: False + metric: 4 + substitue-prefix: 11.1.1.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + address-prefix: 1.1.1.2/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: [] + makes_changes: False + +replaced: + module_args: + state: replaced + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + default_cost: 5 + stub: + enabled: True + no_summary: False + - area_id: 0.0.0.2 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + shortcut: default + default_cost: 3 + stub: + enabled: True + no_summary: True + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + ranges: + - prefix: 1.1.1.1/24 + advertise: True + substitute: 11.2.5.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: True + substitute: 2.5.3.78/24 + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + - router_id: 34.7.35.2 + transmit_delay: 50 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA=" + key_encrypted: True + - key_id: 3 + key: "U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo=" + key_encrypted: True + authentication: + auth_type: message_digest + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + identifier: 0.0.0.1 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.4 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + identifier: 0.0.0.4 + - config: + identifier: 0.0.0.5 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: False + default-cost: 5 + identifier: 0.0.0.5 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + metric: 13 + substitue-prefix: 11.2.5.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: True + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + - address-prefix: 1.1.1.4/24 + config: + advertise: True + metric: 34 + substitue-prefix: 3.3.3.3/24 + address-prefix: 1.1.1.4/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/config/openconfig-ospfv2-ext:shortcut + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/default-cost + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: False + default-cost: 5 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.2 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.4 + identifier: 0.0.0.4 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.2 + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + substitue-prefix: 11.2.5.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: True + substitue-prefix: 2.5.3.78/24 + address-prefix: 1.1.1.3/24 + src-area: 0.0.0.3 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + makes_changes: True + +overridden: + module_args: + state: overridden + config: + - area_id: 0.0.0.1 + vrf_name: Vrf1 + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + default_cost: 5 + stub: + enabled: True + no_summary: False + - area_id: 0.0.0.2 + vrf_name: Vrf1 + filter_list_in: pf1 + filter_list_out: pf2 + shortcut: default + default_cost: 3 + stub: + enabled: True + no_summary: True + authentication_type: message_digest + networks: + - 1.1.1.1/24 + - 3.5.1.5/23 + - 23.235.75.1/23 + - area_id: 3 + vrf_name: Vrf1 + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + ranges: + - prefix: 1.1.1.1/24 + advertise: True + substitute: 11.2.5.1/24 + - prefix: 1.1.1.2/24 + advertise: True + cost: 4 + - prefix: 1.1.1.3/24 + advertise: True + substitute: 2.5.3.78/24 + - area_id: 4 + vrf_name: Vrf1 + shortcut: default + virtual_links: + - router_id: 34.7.35.1 + enabled: True + transmit_delay: 50 + hello_interval: 30 + authentication: + auth_type: text + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + - router_id: 34.7.35.2 + transmit_delay: 50 + enabled: True + dead_interval: 10 + retransmit_interval: 40 + message_digest_list: + - key_id: 1 + key: "U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA=" + key_encrypted: True + - key_id: 3 + key: "U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo=" + key_encrypted: True + authentication: + auth_type: message_digest + key: "U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ=" + key_encrypted: True + existing_config: + - path: data/sonic-vrf:sonic-vrf/VRF/VRF_LIST + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: Vrf1 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + identifier: 0.0.0.1 + identifier: 0.0.0.1 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.2 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.4 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + identifier: 0.0.0.4 + - config: + identifier: 0.0.0.5 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: False + default-cost: 5 + identifier: 0.0.0.5 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + metric: 13 + substitue-prefix: 11.2.5.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: True + substitue-prefix: 2.2.2.2/24 + address-prefix: 1.1.1.3/24 + - address-prefix: 1.1.1.4/24 + config: + advertise: True + metric: 34 + substitue-prefix: 3.3.3.3/24 + address-prefix: 1.1.1.4/24 + filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.1 + expected_config_requests: + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1/ranges/range + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/global/inter-area-propagation-policies/openconfig-ospfv2-ext:inter-area-policy=0.0.0.1 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links/virtual-link + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3 + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/config/openconfig-ospfv2-ext:shortcut + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/default-cost + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/openconfig-ospfv2-ext:stub/config/default-cost + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/openconfig-ospfv2-ext:stub/config/enable + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.5/openconfig-ospfv2-ext:stub/config/no-summary + method: delete + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2 + method: patch + data: + openconfig-network-instance:ospfv2: + areas: + area: + - config: + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.4 + identifier: 0.0.0.4 + - config: + identifier: 0.0.0.3 + identifier: 0.0.0.3 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:shortcut: DEFAULT + identifier: 0.0.0.2 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: True + default-cost: 3 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.2 + - config: + openconfig-ospfv2-ext:authentication-type: MD5HMAC + identifier: 0.0.0.1 + openconfig-ospfv2-ext:stub: + config: + enable: True + no-summary: False + default-cost: 5 + openconfig-ospfv2-ext:networks: + network: + - address-prefix: 1.1.1.1/24 + config: + address-prefix: 1.1.1.1/24 + - address-prefix: 3.5.1.5/23 + config: + address-prefix: 3.5.1.5/23 + - address-prefix: 23.235.75.1/23 + config: + address-prefix: 23.235.75.1/23 + identifier: 0.0.0.1 + global: + inter-area-propagation-policies: + openconfig-ospfv2-ext:inter-area-policy: + - ranges: + range: + - address-prefix: 1.1.1.1/24 + config: + advertise: True + substitue-prefix: 11.2.5.1/24 + address-prefix: 1.1.1.1/24 + - address-prefix: 1.1.1.2/24 + config: + advertise: True + metric: 4 + address-prefix: 1.1.1.2/24 + - address-prefix: 1.1.1.3/24 + config: + advertise: True + substitue-prefix: 2.5.3.78/24 + address-prefix: 1.1.1.3/24 + src-area: 0.0.0.3 + - filter-list-in: + config: + name: pf1 + filter-list-out: + config: + name: pf2 + src-area: 0.0.0.2 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.4/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + - openconfig-ospfv2-ext:md-authentications: + md-authentication: + - authentication-key-id: 1 + config: + authentication-key-encrypted: True + authentication-key-id: 1 + authentication-md5-key: U2FsdGVkX18mUZjlJL/Q/7vYtx2UyDc+NcLKc/BOJUA= + - authentication-key-id: 3 + config: + authentication-key-encrypted: True + authentication-key-id: 3 + authentication-md5-key: U2FsdGVkX19SlRpqsnpeRmjq7WmtctYtveHlYF0Faqo= + config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:dead-interval: 10 + openconfig-ospfv2-ext:retransmission-interval: 40 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: MD5HMAC + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.2 + remote-router-id: 34.7.35.2 + - path: data/openconfig-network-instance:network-instances/network-instance=Vrf1/protocols/protocol=OSPF,ospfv2/ospfv2/areas/area=0.0.0.3/virtual-links + method: patch + data: + openconfig-network-instance:virtual-links: + virtual-link: + - config: + openconfig-ospfv2-ext:enable: True + openconfig-ospfv2-ext:hello-interval: 30 + openconfig-ospfv2-ext:transmit-delay: 50 + openconfig-ospfv2-ext:authentication-type: TEXT + openconfig-ospfv2-ext:authentication-key: U2FsdGVkX18zN46d3pzk+t7TofEHAZGY+5RvgXMwDiQ= + openconfig-ospfv2-ext:authentication-key-encrypted: True + remote-router-id: 34.7.35.1 + remote-router-id: 34.7.35.1 + makes_changes: True diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2.yaml new file mode 100644 index 000000000..9ea3a007d --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2.yaml @@ -0,0 +1,863 @@ +--- +merged_01: + module_args: + config: + - vrf_name: 'default' + router_id: "10.10.10.10" + distance: + external: 20 + redistribute: + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + graceful_restart: + grace_period: 100 + helper: + enable: true + planned_only: true + advertise_router_id: + - "10.10.10.10" + - vrf_name: "VrfReg1" + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: "Eth1/5" + addresses: + - "2.2.2.2" + state: merged + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + graceful-restart: + config: + enabled: true + openconfig-ospfv2-ext:grace-period: 100 + helper-only: true + openconfig-ospfv2-ext:planned-only: true + openconfig-ospfv2-ext:helpers: + helper: + - neighbour-id: "10.10.10.10" + config: + neighbour-id: "10.10.10.10" + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "KERNEL" + direction: "IMPORT" + config: + metric: 15 + openconfig-ospfv2-ext:metric-type: "TYPE_2" + route-map: "rmap_reg1" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "BGP" + direction: "IMPORT" + config: + metric: 15 + openconfig-ospfv2-ext:metric-type: "TYPE_2" + route-map: "rmap_reg1" + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + openconfig-ospfv2-ext:non-passive: true + +merged_02: + module_args: + config: + - vrf_name: "VrfReg2" + write_multiplier: 20 + router_id: "20.20.20.20" + distance: + all: 30 + default_passive: false + passive_interfaces: + - interface: "Eth 1/6" + addresses: + - '3.3.3.3' + - interface: "Eth 1/7" + - abr_type: "cisco" + redistribute: + - protocol: "default_route" + metric: 10 + metric_type: 2 + always: true + - protocol: "kernel" + metric: 15 + metric_type: 2 + route_map: "rmap_reg2" + state: merged + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + non-passive: true + address: '2.2.2.2' + name: 'Eth1/5' + subinterface: 0 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: false + openconfig-ospfv2-ext:write-multiplier: 20 + router-id: '20.20.20.20' + openconfig-ospfv2-ext:distance: + config: + all: 30 + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/6' + subinterface: 0 + address: '3.3.3.3' + config: + openconfig-ospfv2-ext:non-passive: false + - name: 'Eth1/7' + subinterface: 0 + address: '0.0.0.0' + config: + openconfig-ospfv2-ext:non-passive: false + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:abr-type: openconfig-ospfv2-ext:CISCO + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "DEFAULT_ROUTE" + direction: "IMPORT" + config: + metric: 10 + always: true + openconfig-ospfv2-ext:metric-type: "TYPE_2" + - protocol: "KERNEL" + direction: "IMPORT" + config: + route-map: "rmap_reg2" + +deleted_01: + module_args: + config: + - vrf_name: "VrfReg2" + router_id: "20.20.20.20" + passive_interfaces: + - interface: "Eth 1/7" + graceful_restart: + grace_period: 100 + helper: + enable: true + planned_only: true + advertise_router_id: + - "10.10.10.10" + - vrf_name: "VrfReg1" + timers: + throttle_lsa_all: 300 + redistribute: + - protocol: "bgp" + metric: 15 + - protocol: "default_route" + always: true + default_passive: true + non_passive_interfaces: + - interface: "Eth 1/5" + addresses: + - "2.2.2.2" + state: deleted + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + openconfig-ospfv2-ext:abr-type: openconfig-ospfv2-ext:CISCO + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:DEFAULT_ROUTE" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 10 + protocol: "openconfig-ospfv2-ext:DEFAULT_ROUTE" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg2" + always: true + - protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg2" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "20.20.20.20" + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:write-multiplier: 20 + graceful-restart: + config: + enabled: true + openconfig-ospfv2-ext:grace-period: 100 + helper-only: true + openconfig-ospfv2-ext:planned-only: true + openconfig-ospfv2-ext:helpers: + helper: + - neighbour-id: "10.10.10.10" + config: + neighbour-id: "10.10.10.10" + openconfig-ospfv2-ext:distance: + config: + all: 30 + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/6' + subinterface: 0 + address: '3.3.3.3' + config: + non-passive: false + address: '3.3.3.3' + name: 'Eth1/6' + subinterface: 0 + - name: 'Eth1/7' + subinterface: 0 + address: '0.0.0.0' + config: + non-passive: false + address: '0.0.0.0' + name: 'Eth1/7' + subinterface: 0 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + non-passive: true + address: '2.2.2.2' + name: 'Eth1/5' + subinterface: 0 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + - protocol: "openconfig-ospfv2-ext:DEFAULT_ROUTE" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + always: true + protocol: "openconfig-ospfv2-ext:DEFAULT_ROUTE" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + openconfig-ospfv2-ext:non-passive: false + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global/config/openconfig-ospfv2-ext:passive-interface-default" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list=BGP,IMPORT" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global/openconfig-ospfv2-ext:route-distribution-policies/distribute-list=DEFAULT_ROUTE,IMPORT/config/always" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global/timers/lsa-generation/config/openconfig-ospfv2-ext:minimum-interval" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/config/router-id" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/openconfig-ospfv2-ext:passive-interfaces/passive-interface=Eth1%2f7,0,0.0.0.0" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/graceful-restart/config/helper-only" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/graceful-restart/config/openconfig-ospfv2-ext:grace-period" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/graceful-restart/config/openconfig-ospfv2-ext:planned-only" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global/graceful-restart/openconfig-ospfv2-ext:helpers/helper=10.10.10.10" + method: "delete" + +deleted_02: + module_args: + config: + state: deleted + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + non-passive: true + address: '2.2.2.2' + name: 'Eth1/5' + subinterface: 0 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + +replaced_01: + module_args: + config: + - vrf_name: "VrfReg2" + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: "Eth 1/5" + addresses: + - "2.2.2.2" + - vrf_name: "default" + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "connected" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + state: replaced + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:write-multiplier: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + max-metric: + config: + openconfig-ospfv2-ext:administrative: true + openconfig-ospfv2-ext:external-lsa-connected: 2127 + openconfig-ospfv2-ext:on-startup: 20 + openconfig-ospfv2-ext:router-lsa-stub: 2128 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:write-multiplier: 20 + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + openconfig-ospfv2-ext:non-passive: true + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "BGP" + direction: "IMPORT" + config: + metric: 15 + openconfig-ospfv2-ext:metric-type: 'TYPE_2' + route-map: 'rmap_reg1' + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "DIRECTLY_CONNECTED" + direction: "IMPORT" + config: + metric: 15 + openconfig-ospfv2-ext:metric-type: 'TYPE_2' + route-map: 'rmap_reg1' + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + +overridden_01: + module_args: + config: + - vrf_name: 'default' + write_multiplier: 20 + timers: + throttle_lsa_all: 300 + throttle_spf: + delay_time: 10 + initial_hold_time: 20 + maximum_hold_time: 50 + redistribute: + - protocol: "bgp" + metric: 15 + metric_type: 2 + route_map: "rmap_reg1" + default_passive: true + non_passive_interfaces: + - interface: "Eth 1/5" + addresses: + - "2.2.2.2" + state: overridden + existing_ospfv2_config: + - path: "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST" + response: + code: 200 + value: + sonic-vrf:VRF_LIST: + - vrf_name: default + - vrf_name: VrfReg1 + - vrf_name: VrfReg2 + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + router-id: "10.10.10.10" + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:distance: + config: + external: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:KERNEL" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:write-multiplier: 20 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + max-metric: + config: + openconfig-ospfv2-ext:administrative: true + openconfig-ospfv2-ext:external-lsa-connected: 2127 + openconfig-ospfv2-ext:on-startup: 20 + openconfig-ospfv2-ext:router-lsa-stub: 2128 + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2" + response: + code: 200 + value: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:enable: true + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + non-passive: true + address: '2.2.2.2' + name: 'Eth1/5' + subinterface: 0 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + config: + metric: 15 + protocol: "openconfig-ospfv2-ext:BGP" + direction: "openconfig-ospfv2-ext:IMPORT" + metric-type: "openconfig-ospfv2-ext:TYPE_2" + route-map: "rmap_reg1" + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + expected_config_requests: + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg1/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=VrfReg2/protocols/protocol=OSPF,ospfv2/ospfv2/global" + method: "delete" + - path: "/data/openconfig-network-instance:network-instances/network-instance=default/protocols/protocol=OSPF,ospfv2/ospfv2" + method: "patch" + data: + openconfig-network-instance:ospfv2: + global: + config: + openconfig-ospfv2-ext:passive-interface-default: true + openconfig-ospfv2-ext:write-multiplier: 20 + timers: + lsa-generation: + config: + openconfig-ospfv2-ext:minimum-interval: 300 + spf: + config: + initial-delay: 20 + maximum-delay: 50 + openconfig-ospfv2-ext:throttle-delay: 10 + openconfig-ospfv2-ext:route-distribution-policies: + distribute-list: + - protocol: "BGP" + direction: "IMPORT" + config: + metric: 15 + openconfig-ospfv2-ext:metric-type: 'TYPE_2' + route-map: 'rmap_reg1' + openconfig-ospfv2-ext:passive-interfaces: + passive-interface: + - name: 'Eth1/5' + subinterface: 0 + address: '2.2.2.2' + config: + openconfig-ospfv2-ext:non-passive: true diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2_interfaces.yaml new file mode 100644 index 000000000..2d62aa053 --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_ospfv2_interfaces.yaml @@ -0,0 +1,1486 @@ +--- +merged_01: + module_args: + config: + - name: 'Eth1/5' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: broadcast + - name: 'PortChannel100' + ospf_attributes: + - area_id: '3.3.3.3' + address: '10.19.120.2' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + network: point_to_point + - name: 'Vlan100' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 20 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + state: merged + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + hello-interval: 10 + metric: 20 + mtu-ignore: true + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + priority: 20 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '10.19.120.2' + config: + address: '10.19.120.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + area-id: '3.3.3.3' + - address: '0.0.0.0' + config: + address: '0.0.0.0' + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + enabled: true + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + hello-multiplier: 2 + dead-interval-minimal: true + metric: 20 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + +merged_02: + module_args: + config: + - name: 'PortChannel100' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 20 + priority: 15 + - address: '10.10.120.1' + mtu_ignore: True + authentication_type: 'TEXT' + md_authentication: + - key_id: 30 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + network: point_to_point + bfd: + enable: True + bfd_profile: 'profile2' + - name: 'Eth1/6' + ospf_attributes: + - area_id: '4.4.4.4' + cost: 15 + hello_multiplier: 2 + - address: '10.10.120.1' + authentication_type: 'NONE' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: 'Vlan100' + bfd: + enable: True + bfd_profile: 'profile2' + network: broadcast + ospf_attributes: + - area_id: '1.1.1.1' + address: '10.19.120.2' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - area_id: '2.2.2.2' + address: '10.19.120.3' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - name: 'Loopback100' + ospf_attributes: + - area_id: '2.2.2.2' + cost: 20 + priority: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + bfd: + enable: True + bfd_profile: 'profile1' + network: point_to_point + state: merged + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + dead-interval-minimal: false + hello-interval: 10 + metric: 20 + mtu-ignore: true + priority: 20 + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + dead-interval-minimal: true + hello-multiplier: 5 + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '3.3.3.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 20 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Loopback100' + subinterfaces: + subinterface: + - index: 0 + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '4.4.4.4' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + area-id: '2.2.2.2' + dead-interval: 40 + hello-interval: 10 + mtu-ignore: true + metric: 20 + priority: 20 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'TEXT' + mtu-ignore: true + md-authentications: + md-authentication: + - authentication-key-id: 30 + config: + authentication-key-id: 30 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + bfd-profile: 'profile2' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + - address: '10.19.120.2' + config: + address: '10.19.120.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + area-id: '1.1.1.1' + - address: '10.19.120.3' + config: + address: '10.19.120.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + area-id: '2.2.2.2' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + bfd-profile: 'profile2' + enabled: true + +deleted_01: + module_args: + config: + - name: 'Eth1/5' + - name: 'Eth1/6' + ospf_attributes: + - area_id: '4.4.4.4' + cost: 15 + hello_multiplier: 5 + - address: '10.10.120.1' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: 'PortChannel100' + ospf_attributes: + - address: '0.0.0.0' + - address: '10.19.120.2' + - name: 'Vlan100' + bfd: + enable: True + network: 'broadcast' + ospf_attributes: + - address: '10.10.120.1' + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + state: deleted + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + dead-interval-minimal: false + hello-interval: 10 + metric: 20 + mtu-ignore: true + priority: 20 + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '4.4.4.4' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + dead-interval-minimal: true + hello-multiplier: 5 + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '3.3.3.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'TEXT' + mtu-ignore: true + md-authentications: + md-authentication: + - authentication-key-id: 30 + config: + authentication-key-id: 30 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 20 + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile2' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.19.120.3' + config: + address: '10.19.120.3' + area-id: '2.2.2.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - name: 'Loopback100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + hello-interval: 10 + dead-interval-minimal: false + dead-interval: 40 + mtu-ignore: true + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/md-authentications/md-authentication=10' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/priority' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/network-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/enable-bfd' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/md-authentications/md-authentication=10' + method: 'delete' + +deleted_02: + module_args: + config: + state: deleted + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + dead-interval-minimal: false + hello-interval: 10 + metric: 20 + mtu-ignore: true + priority: 20 + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '4.4.4.4' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + dead-interval-minimal: true + hello-multiplier: 5 + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '3.3.3.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'TEXT' + mtu-ignore: true + md-authentications: + md-authentication: + - authentication-key-id: 30 + config: + authentication-key-id: 30 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 20 + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile2' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.19.120.3' + config: + address: '10.19.120.3' + area-id: '2.2.2.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - name: 'Loopback100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + hello-interval: 10 + dead-interval-minimal: false + dead-interval: 40 + mtu-ignore: true + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + +replaced_01: + module_args: + config: + - name: 'Eth1/5' + bfd: + bfd_profile: 'profile1' + enable: True + ospf_attributes: + - area_id: '33686018' + cost: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + priority: 20 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + md_authentication: + - key_id: 10 + md5key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + - name: 'Loopback100' + network: broadcast + bfd: + bfd_profile: 'profile3' + enable: True + - name: 'Vlan100' + ospf_attributes: + - address: '10.10.120.2' + area_id: '1.1.1.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - address: '10.10.120.3' + area_id: '2.2.2.2' + network: broadcast + state: replaced + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + dead-interval-minimal: false + hello-interval: 10 + metric: 20 + mtu-ignore: true + priority: 20 + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + md-authentications: + md-authentication: + - authentication-key-id: 15 + config: + authentication-key-id: 15 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '4.4.4.4' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + dead-interval-minimal: true + hello-multiplier: 5 + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '3.3.3.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'TEXT' + mtu-ignore: true + md-authentications: + md-authentication: + - authentication-key-id: 30 + config: + authentication-key-id: 30 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 20 + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile2' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.19.120.3' + config: + address: '10.19.120.3' + area-id: '2.2.2.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - name: 'Loopback100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + hello-interval: 10 + dead-interval-minimal: false + dead-interval: 40 + mtu-ignore: true + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/hello-interval' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/mtu-ignore' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/priority' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/enable-bfd' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/md-authentications/md-authentication=10' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/md-authentications/md-authentication=15' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + bfd-profile: 'profile3' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '10.10.120.2' + config: + address: '10.10.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.10.120.3' + config: + address: '10.10.120.3' + area-id: '2.2.2.2' + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '10.10.120.1' + config: + address: '10.10.120.1' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-encrypted: true + authentication-key-id: 10 + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + +overridden_01: + module_args: + config: + - name: 'Eth1/5' + bfd: + bfd_profile: 'profile1' + enable: True + ospf_attributes: + - area_id: 33686018 + cost: 20 + hello_interval: 10 + dead_interval: 40 + mtu_ignore: True + priority: 20 + - address: '10.10.120.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 5 + - name: 'Loopback100' + network: broadcast + bfd: + bfd_profile: 'profile3' + enable: True + - name: 'Vlan100' + ospf_attributes: + - address: '10.10.120.2' + area_id: '1.1.1.1' + authentication_type: 'MD5HMAC' + authentication: + password: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + encrypted: True + hello_multiplier: 2 + - address: '10.10.120.3' + area_id: '2.2.2.2' + network: broadcast + state: overridden + existing_ospfv2_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval: 40 + dead-interval-minimal: false + hello-interval: 10 + metric: 20 + mtu-ignore: true + priority: 20 + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '4.4.4.4' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 15 + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + dead-interval-minimal: true + hello-multiplier: 5 + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '3.3.3.3' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'TEXT' + mtu-ignore: true + md-authentications: + md-authentication: + - authentication-key-id: 30 + config: + authentication-key-id: 30 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + dead-interval-minimal: true + hello-multiplier: 2 + metric: 20 + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile2' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'NONE' + md-authentications: + md-authentication: + - authentication-key-id: 10 + config: + authentication-key-id: 10 + authentication-key-encrypted: true + authentication-md5-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + - address: '10.19.120.2' + config: + address: '10.19.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.19.120.3' + config: + address: '10.19.120.3' + area-id: '2.2.2.2' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - name: 'Loopback100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + area-id: '2.2.2.2' + metric: 20 + priority: 20 + hello-interval: 10 + dead-interval-minimal: false + dead-interval: 40 + mtu-ignore: true + network-type: 'openconfig-ospf-types:POINT_TO_POINT_NETWORK' + enable-bfd: + config: + enabled: true + bfd-profile: 'profile1' + - address: '10.10.120.1' + config: + address: '10.10.120.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 5 + dead-interval-minimal: true + expected_config_requests: + - path: '/data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/hello-interval' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/mtu-ignore' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/priority' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/enable-bfd' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/metric' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=0.0.0.0/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.10.120.1/md-authentications/md-authentication=10' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.2/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/area-id' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/authentication-key' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/authentication-type' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2/if-addresses=10.19.120.3/config/dead-interval-minimal' + method: 'delete' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + config: + address: '0.0.0.0' + network-type: 'openconfig-ospf-types:BROADCAST_NETWORK' + - path: '/data/openconfig-interfaces:interfaces/interface=Loopback100/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '0.0.0.0' + enable-bfd: + config: + bfd-profile: 'profile3' + - path: '/data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv4/openconfig-ospfv2-ext:ospfv2' + method: 'patch' + data: + openconfig-ospfv2-ext:ospfv2: + if-addresses: + - address: '10.10.120.2' + config: + address: '10.10.120.2' + area-id: '1.1.1.1' + authentication-type: 'MD5HMAC' + authentication-key: 'U2FsdGVkX19eY7P3qRyyjaFsQgjoSQE71IX6IeBRios=' + authentication-key-encrypted: true + hello-multiplier: 2 + dead-interval-minimal: true + - address: '10.10.120.3' + config: + address: '10.10.120.3' + area-id: '2.2.2.2' diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_route_maps.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_route_maps.yaml index 2a783d276..53f34a9df 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_route_maps.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_route_maps.yaml @@ -118,6 +118,19 @@ merged_02: value: 870 origin: egp weight: 93471 + tag: 65 + - map_name: rm6 + action: permit + sequence_num: 80 + set: + as_path_prepend: 0.200,315,7135,10.4 + extcommunity: + rt: + - "30:40" + - "10.3:41" + soo: + - "10.73.14.9:78" + - "200.4:79" existing_route_maps_config: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" response: @@ -229,10 +242,37 @@ merged_02: set-med: 870 set-route-origin: EGP set-weight: 93471 + set-tag: 65 metric-action: config: metric: 870 action: openconfig-routing-policy:METRIC_SET_VALUE + - name: rm6 + config: + name: rm6 + statements: + statement: + - name: "80" + config: + name: "80" + actions: + config: + policy-result: ACCEPT_ROUTE + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: "0.200,315,7135,10.4" + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:30:40 + - route-target:10.3:41 + - route-origin:10.73.14.9:78 + - route-origin:200.4:79 merged_03: module_args: @@ -304,6 +344,7 @@ merged_03: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "200,315,7135" @@ -527,6 +568,7 @@ merged_04: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "200,315,7135" @@ -774,6 +816,18 @@ merged_05: prefer_global: false metric: rtt_action: add + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.200,315,7135,10.4 + extcommunity: + rt: + - "30:40" + - "10.3:41" + soo: + - "10.73.14.9:78" + - "200.4:79" existing_route_maps_config: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" response: @@ -796,6 +850,7 @@ merged_05: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "200,315,7135" @@ -985,6 +1040,53 @@ merged_05: config: name: "480" name: "480" + - config: + name: rm6 + name: rm6 + statements: + statement: + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 200,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "80" + name: "80" + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 100,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:409 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "100" + name: "100" expected_config_requests: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" method: patch @@ -1019,6 +1121,29 @@ merged_05: metric-action: config: action: openconfig-routing-policy:METRIC_ADD_RTT + - name: rm6 + config: + name: rm6 + statements: + statement: + - name: "100" + config: + name: "100" + actions: + config: + policy-result: ACCEPT_ROUTE + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: "0.200,315,7135,10.4" + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:30:40 merged_06: module_args: @@ -1080,6 +1205,7 @@ merged_06: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -1392,6 +1518,18 @@ replaced_01: set: ipv6_next_hop: global_addr: 45::90 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.200,315,7135,10.4 + extcommunity: + rt: + - "30:40" + - "10.3:241" + soo: + - "200.4:79" + - "200.4:279" state: replaced existing_route_maps_config: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" @@ -1415,6 +1553,7 @@ replaced_01: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -1650,6 +1789,53 @@ replaced_01: config: name: "480" name: "480" + - config: + name: rm6 + name: rm6 + statements: + statement: + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 200,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "80" + name: "80" + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 100,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "100" + name: "100" expected_config_requests: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" method: "patch" @@ -1711,6 +1897,32 @@ replaced_01: openconfig-bgp-policy:bgp-actions: config: set-ipv6-next-hop-global: 45::90 + - name: rm6 + config: + name: rm6 + statements: + statement: + - name: "100" + config: + name: "100" + actions: + config: + policy-result: ACCEPT_ROUTE + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: "0.200,315,7135,10.4" + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:30:40 + - route-target:10.3:241 + - route-origin:200.4:79 + - route-origin:200.4:279 - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/config/set-ipv6-next-hop-prefer-global" method: "delete" data: @@ -1744,6 +1956,18 @@ replaced_01: communities: - route-target:30:40 - route-origin:10.73.14.9:78 + - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm6/statements/statement=100/actions/openconfig-bgp-policy:bgp-actions/set-ext-community" + method: "patch" + data: + openconfig-bgp-policy:set-ext-community: + config: + method: INLINE + options: REMOVE + inline: + config: + communities: + - route-target:655363:41 + - route-origin:10.73.14.9:78 - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/conditions/match-prefix-set/config/prefix-set" method: "delete" data: @@ -1792,6 +2016,7 @@ replaced_02: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -2158,6 +2383,7 @@ replaced_03: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -2479,6 +2705,9 @@ replaced_03: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/config/set-weight" method: "delete" data: + - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/config/set-tag" + method: "delete" + data: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/set-as-path-prepend" method: "delete" data: @@ -2564,6 +2793,18 @@ overridden_01: match: evpn: vni: 735 + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.200,315,7135,10.4 + extcommunity: + rt: + - "30:40" + - "10.3:241" + soo: + - "200.4:79" + - "200.4:279" state: overridden existing_route_maps_config: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" @@ -2586,6 +2827,7 @@ overridden_01: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -2821,6 +3063,53 @@ overridden_01: config: name: "480" name: "480" + - config: + name: rm6 + name: rm6 + statements: + statement: + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 200,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "80" + name: "80" + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 100,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "100" + name: "100" expected_config_requests: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" method: "delete" @@ -2846,6 +3135,32 @@ overridden_01: openconfig-policy-ext:match-evpn-set: config: vni-number: 735 + - name: rm6 + config: + name: rm6 + statements: + statement: + - name: "100" + config: + name: "100" + actions: + config: + policy-result: ACCEPT_ROUTE + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: "0.200,315,7135,10.4" + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:30:40 + - route-target:10.3:241 + - route-origin:200.4:79 + - route-origin:200.4:279 overridden_02: module_args: @@ -2878,6 +3193,7 @@ overridden_02: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -3351,6 +3667,7 @@ deleted_01: rtt_action: add origin: egp weight: 93471 + tag: 65 - map_name: rm1 action: deny sequence_num: 3047 @@ -3394,6 +3711,18 @@ deleted_01: sequence_num: 480 match: source_protocol: static + - map_name: rm6 + action: permit + sequence_num: 100 + set: + as_path_prepend: 0.200,315,7135,10.4 + extcommunity: + rt: + - "30:40" + - "10.3:241" + soo: + - "200.4:79" + - "200.4:279" state: deleted existing_route_maps_config: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions" @@ -3417,6 +3746,7 @@ deleted_01: set-next-hop: 10.48.16.18 set-route-origin: EGP set-weight: 93471 + set-tag: 65 set-as-path-prepend: config: openconfig-routing-policy-ext:asn-list: "188,257" @@ -3653,6 +3983,53 @@ deleted_01: config: name: "480" name: "480" + - config: + name: rm6 + name: rm6 + statements: + statement: + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 200,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "80" + name: "80" + - actions: + openconfig-bgp-policy:bgp-actions: + set-as-path-prepend: + config: + openconfig-routing-policy-ext:asn-list: 100,0.315,0.7135,655364 + set-ext-community: + config: + method: INLINE + options: ADD + inline: + config: + communities: + - route-target:0.30:40 + - route-target:655363:41 + - route-origin:10.73.14.9:78 + - route-origin:13107204:79 + config: + policy-result: ACCEPT_ROUTE + config: + name: "100" + name: "100" expected_config_requests: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=3047/actions/metric-action/config" method: "delete" @@ -3690,6 +4067,9 @@ deleted_01: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/config/set-weight" method: "delete" data: + - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/config/set-tag" + method: "delete" + data: - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/actions/openconfig-bgp-policy:bgp-actions/set-as-path-prepend" method: "delete" data: @@ -3720,6 +4100,18 @@ deleted_01: communities: - route-target:30:40 - route-origin:10.73.14.9:78 + - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm6/statements/statement=100/actions/openconfig-bgp-policy:bgp-actions/set-ext-community" + method: "patch" + data: + openconfig-bgp-policy:set-ext-community: + config: + method: INLINE + options: REMOVE + inline: + config: + communities: + - route-target:30:40 + - route-origin:200.4:79 - path: "data/openconfig-routing-policy:routing-policy/policy-definitions/policy-definition=rm1/statements/statement=80/conditions/match-interface" method: "delete" data: diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_system.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_system.yaml index 4027b65e1..697058e91 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_system.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_system.yaml @@ -1,22 +1,23 @@ --- - merged_01: module_args: config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 interface_naming: standard anycast_address: ipv6: true ipv4: true mac_address: aa:bb:cc:dd:ee:ff auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_HI + audit_rules: DETAIL existing_system_config: - path: "data/openconfig-system:system/config" response: code: 200 value: openconfig-system:config: - hostname: abcd_host + hostname: abcd-host anycast_address: IPv4: true mac_address: 11:22:33:44:55:66 @@ -26,16 +27,30 @@ merged_01: value: sonic-device-metadata:DEVICE_METADATA_LIST: - intf_naming_mode: native - - auto-breakout: DISABLE + auto-breakout: DISABLE - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" response: code: 200 + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 + value: + openconfig-loadshare-mode-ext:config: + algorithm: CRC_XOR + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system" + response: + code: 200 + value: + openconfig-system-ext:auditd-system: + config: + audit-rules: BASIC + expected_config_requests: - path: "data/openconfig-system:system/config" method: "patch" data: openconfig-system:config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" method: "patch" data: @@ -52,19 +67,27 @@ merged_01: method: "patch" data: sonic-device-metadata:auto-breakout: ENABLE + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + method: "patch" + data: + openconfig-loadshare-mode-ext:config: + algorithm: JENKINS_HASH_HI + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules" + method: "patch" + data: + openconfig-system-ext:audit-rules: DETAIL merged_02: module_args: config: interface_naming: standard_extended - existing_system_config: - path: "data/openconfig-system:system/config" response: code: 200 value: openconfig-system:config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 anycast_address: IPv4: true IPv6: true @@ -78,6 +101,9 @@ merged_02: - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" response: code: 200 + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 expected_config_requests: - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" method: "patch" @@ -93,14 +119,14 @@ deleted_01: code: 200 value: openconfig-system:config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost" response: code: 200 value: sonic-device-metadata:DEVICE_METADATA_LIST: - intf_naming_mode: standard-ext - - auto-breakout: ENABLE + auto-breakout: ENABLE - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" response: code: 200 @@ -110,8 +136,22 @@ deleted_01: IPv6: enable gwmac: aa:bb:cc:dd:ee:ff table_distinguisher: IP + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 + value: + openconfig-loadshare-mode-ext:config: + algorithm: JENKINS_HASH_HI + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system" + response: + code: 200 + value: + openconfig-system-ext:auditd-system: + config: + audit-rules: BASIC + expected_config_requests: - - path: "data/openconfig-system:system/config/" + - path: "data/openconfig-system:system/config" method: "patch" data: openconfig-system:config: @@ -125,25 +165,31 @@ deleted_01: - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" method: "delete" data: + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config/algorithm" + method: "delete" + - path: 'data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules' + method: 'delete' + data: deleted_02: module_args: state: deleted config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 interface_naming: standard_extended anycast_address: ipv6: true ipv4: true mac_address: aa:bb:cc:dd:ee:ff auto_breakout: ENABLE + load_share_hash_algo: JENKINS_HASH_HI existing_system_config: - path: "data/openconfig-system:system/config" response: code: 200 value: openconfig-system:config: - hostname: SONIC_Test1 + hostname: SONIC-Test1 - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost" response: code: 200 @@ -159,8 +205,14 @@ deleted_02: IPv6: enable gwmac: aa:bb:cc:dd:ee:ff table_distinguisher: IP + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 + value: + openconfig-loadshare-mode-ext:config: + algorithm: JENKINS_HASH_HI expected_config_requests: - - path: "data/openconfig-system:system/config/" + - path: "data/openconfig-system:system/config" method: "patch" data: openconfig-system:config: @@ -174,32 +226,37 @@ deleted_02: - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" method: "delete" data: + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config/algorithm" + method: "delete" + data: replaced_01: module_args: state: replaced config: - hostname: SONIC_Test11 - interface_naming: standard_extended + hostname: SONIC-Test11 anycast_address: - ipv6: False + ipv6: false ipv4: true mac_address: 11:22:33:44:55:66 auto_breakout: DISABLE + load_share_hash_algo: CRC_XOR + audit_rules: BASIC + existing_system_config: - path: "data/openconfig-system:system/config" response: code: 200 value: openconfig-system:config: - hostname: abcd_host + hostname: abcd-host - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost" response: code: 200 value: sonic-device-metadata:DEVICE_METADATA_LIST: - intf_naming_mode: standard - - auto-breakout: ENABLE + auto-breakout: ENABLE - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" response: code: 200 @@ -209,72 +266,77 @@ replaced_01: IPv6: enable gwmac: aa:bb:cc:dd:ee:ff table_distinguisher: IP + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 + value: + openconfig-loadshare-mode-ext:config: + algorithm: JENKINS_HASH_HI + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system" + response: + code: 200 + value: + openconfig-system-ext:auditd-system: + config: + audit-rules: DETAIL + expected_config_requests: - - path: "data/openconfig-system:system/config/" - method: "patch" - data: - openconfig-system:config: - hostname: sonic - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" method: "delete" data: - - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" - method: "delete" - data: - - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv4" - method: "delete" - - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv6" - method: "delete" - - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/gwmac" - method: "delete" - data: - path: "data/openconfig-system:system/config" method: "patch" data: openconfig-system:config: - hostname: SONIC_Test11 - - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" - method: "patch" - data: - sonic-device-metadata:intf_naming_mode: standard-ext + hostname: SONIC-Test11 - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" method: "patch" data: sonic-sag:SAG_GLOBAL_LIST: - - IPv4: enable - IPv6: disable + - IPv6: disable gwmac: 11:22:33:44:55:66 table_distinguisher: IP - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" method: "patch" data: sonic-device-metadata:auto-breakout: DISABLE + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + method: "patch" + data: + openconfig-loadshare-mode-ext:config: + algorithm: CRC_XOR + + + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules" + method: "patch" + data: + openconfig-system-ext:audit-rules: BASIC overridden_01: module_args: state: overridden config: - hostname: SONIC_Test11 + hostname: SONIC-Test11 interface_naming: native anycast_address: - ipv6: False + ipv6: false ipv4: true - mac_address: 00:09:5B:EC:EE:F2 - auto_breakout: DISABLE + load_share_hash_algo: CRC_XOR + existing_system_config: - path: "data/openconfig-system:system/config" response: code: 200 value: openconfig-system:config: - hostname: abcd_host + hostname: abcd-host - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost" response: code: 200 value: sonic-device-metadata:DEVICE_METADATA_LIST: - intf_naming_mode: standard - - auto-breakout: ENABLE + auto-breakout: ENABLE - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/" response: code: 200 @@ -284,30 +346,32 @@ overridden_01: IPv6: enable gwmac: aa:bb:cc:dd:ee:ff table_distinguisher: IP + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" + response: + code: 200 + value: + openconfig-loadshare-mode-ext:config: + algorithm: JENKINS_HASH_HI + - path: "data/openconfig-system:system/openconfig-system-ext:auditd-system" + response: + code: 200 + value: + openconfig-system-ext:auditd-system: + config: + audit-rules: DETAIL + expected_config_requests: - - path: "data/openconfig-system:system/config/" - method: "patch" - data: - openconfig-system:config: - hostname: sonic - - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" - method: "delete" - data: - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" method: "delete" data: - - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv4" - method: "delete" - - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv6" - method: "delete" - path: "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/gwmac" method: "delete" - data: + data: - path: "data/openconfig-system:system/config" method: "patch" data: openconfig-system:config: - hostname: SONIC_Test11 + hostname: SONIC-Test11 - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode" method: "patch" data: @@ -316,11 +380,13 @@ overridden_01: method: "patch" data: sonic-sag:SAG_GLOBAL_LIST: - - IPv4: enable - IPv6: disable - gwmac: 00:09:5B:EC:EE:F2 + - IPv6: disable table_distinguisher: IP - - path: "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/auto-breakout" + - path: "data/openconfig-loadshare-mode-ext:loadshare/hash-algorithm/config" method: "patch" data: - sonic-device-metadata:auto-breakout: DISABLE + openconfig-loadshare-mode-ext:config: + algorithm: CRC_XOR + - path: 'data/openconfig-system:system/openconfig-system-ext:auditd-system/config/audit-rules' + method: 'delete' + data: diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_vrrp.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_vrrp.yaml new file mode 100644 index 000000000..fa20a7dfa --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_vrrp.yaml @@ -0,0 +1,592 @@ +--- +merged_01: + module_args: + config: + - name: Eth1/5 + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 80.1.1.3 + - address: 80.1.1.4 + preempt: True + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 80::3 + - address: 80::4 + advertisement_interval: 4 + priority: 10 + - name: Eth1/6 + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 90.1.1.3 + priority: 20 + track_interface: + - interface: Eth1/11 + priority_increment: 10 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 90.1.1.4 + preempt: True + priority: 20 + state: merged + existing_vrrp_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + expected_config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 1 + config: + virtual-router-id: 1 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=1/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - '80.1.1.3' + - '80.1.1.4' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=1/config/preempt' + method: 'patch' + data: + openconfig-if-ip:preempt: True + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 10 + config: + virtual-router-id: 10 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - 80::3 + - 80::4 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/advertisement-interval' + method: 'patch' + data: + openconfig-if-ip:advertisement-interval: 4 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/priority' + method: 'patch' + data: + openconfig-if-ip:priority: 10 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 5 + config: + virtual-router-id: 5 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - '90.1.1.3' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/config/priority' + method: 'patch' + data: + openconfig-if-ip:priority: 20 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/openconfig-interfaces-ext:vrrp-track' + method: 'patch' + data: + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/11 + track-intf: Eth1/11 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 15 + config: + virtual-router-id: 15 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - '90.1.1.4' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15/config/preempt' + method: 'patch' + data: + openconfig-if-ip:preempt: true + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15/config/priority' + method: 'patch' + data: + openconfig-if-ip:priority: 20 + +deleted_01: + module_args: + config: + - name: Eth1/5 + group: + - virtual_router_id: 1 + afi: ipv4 + virtual_address: + - address: 80.1.1.3 + preempt: True + - virtual_router_id: 10 + afi: ipv6 + advertisement_interval: 4 + priority: 10 + - name: Eth1/6 + group: + - virtual_router_id: 5 + afi: ipv4 + virtual_address: + - address: 90.1.1.3 + priority: 20 + track_interface: + - interface: Eth1/11 + priority_increment: 10 + - virtual_router_id: 15 + afi: ipv4 + virtual_address: + - address: 90.1.1.4 + preempt: True + priority: 20 + state: deleted + existing_vrrp_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 1 + advertisement-interval: 1 + preempt: True + priority: 100 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 80.1.1.3 + - 80.1.1.4 + openconfig-if-ip:ipv6: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 10 + advertisement-interval: 4 + priority: 10 + preempt: True + virtual-address: + - 80::3 + - 80::4 + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + advertisement-interval: + virtual-router-id: 5 + preempt: True + priority: 20 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.3 + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/11 + track-intf: Eth1/11 + - config: + virtual-router-id: 15 + advertisement-interval: 1 + priority: 20 + preempt: True + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.4 + expected_config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=1/config/virtual-address=80.1.1.3' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/advertisement-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/priority' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/config/virtual-address=90.1.1.3' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/config/priority' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5/openconfig-interfaces-ext:vrrp-track/vrrp-track-interface=Eth1%2f11' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15/config/virtual-address=90.1.1.4' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15/config/priority' + method: 'delete' + +deleted_02: + module_args: + config: + state: deleted + existing_vrrp_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 1 + advertisement-interval: 1 + preempt: True + priority: 100 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 80.1.1.3 + - 80.1.1.4 + openconfig-if-ip:ipv6: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 10 + advertisement-interval: 4 + priority: 10 + preempt: True + virtual-address: + - 80::3 + - 80::4 + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + advertisement-interval: + virtual-router-id: 5 + preempt: True + priority: 20 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.3 + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/11 + track-intf: Eth1/11 + - config: + virtual-router-id: 15 + advertisement-interval: 1 + priority: 20 + preempt: True + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.4 + expected_config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=1' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15' + method: 'delete' + +replaced_01: + module_args: + config: + - name: 'Eth1/5' + group: + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 90::2 + preempt: False + track_interface: + - priority_increment: 10 + interface: 'Eth1/11' + state: replaced + existing_vrrp_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 1 + advertisement-interval: 1 + preempt: True + priority: 100 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 80.1.1.3 + - 80.1.1.4 + openconfig-if-ip:ipv6: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 10 + advertisement-interval: 4 + priority: 10 + preempt: True + virtual-address: + - 80::3 + - 80::4 + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/12 + track-intf: Eth1/12 + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + advertisement-interval: + virtual-router-id: 5 + preempt: True + priority: 20 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.3 + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/11 + track-intf: Eth1/11 + - config: + virtual-router-id: 15 + advertisement-interval: 1 + priority: 20 + preempt: True + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.4 + expected_config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/advertisement-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/priority' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address=80::3' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address=80::4' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/openconfig-interfaces-ext:vrrp-track/vrrp-track-interface=Eth1%2f12' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 10 + config: + virtual-router-id: 10 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/openconfig-interfaces-ext:vrrp-track' + method: 'patch' + data: + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: 'Eth1/11' + track-intf: 'Eth1/11' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - 90::2 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/preempt' + method: 'patch' + data: + openconfig-if-ip:preempt: False + +overridden_01: + module_args: + config: + - name: 'Eth1/5' + group: + - virtual_router_id: 10 + afi: ipv6 + virtual_address: + - address: 90::2 + preempt: False + state: overridden + existing_vrrp_config: + - path: 'data/openconfig-interfaces:interfaces' + response: + code: 200 + value: + openconfig-interfaces:interfaces: + interface: + - name: 'Eth1/5' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 1 + advertisement-interval: 1 + preempt: True + priority: 100 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 80.1.1.3 + - 80.1.1.4 + openconfig-if-ip:ipv6: + addresses: + address: + - vrrp: + vrrp-group: + - config: + virtual-router-id: 10 + advertisement-interval: 4 + priority: 10 + preempt: True + virtual-address: + - 80::3 + - 80::4 + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv4: + addresses: + address: + - vrrp: + vrrp-group: + - config: + advertisement-interval: + virtual-router-id: 5 + preempt: True + priority: 20 + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.3 + openconfig-interfaces-ext:vrrp-track: + vrrp-track-interface: + - config: + priority-increment: 10 + track-intf: Eth1/11 + track-intf: Eth1/11 + - config: + virtual-router-id: 15 + advertisement-interval: 1 + priority: 20 + preempt: True + openconfig-interfaces-ext:use-v2-checksum: false + openconfig-interfaces-ext:version: 2 + virtual-address: + - 90.1.1.4 + expected_config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=1' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/advertisement-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/priority' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address=80::3' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address=80::4' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=5' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address=1.1.1.1/vrrp/vrrp-group=15' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp' + method: 'patch' + data: + openconfig-if-ip:vrrp: + vrrp-group: + - virtual-router-id: 10 + config: + virtual-router-id: 10 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/virtual-address' + method: 'patch' + data: + openconfig-if-ip:virtual-address: + - 90::2 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address=1::1/vrrp/vrrp-group=10/config/preempt' + method: 'patch' + data: + openconfig-if-ip:preempt: False diff --git a/tests/unit/modules/network/sonic/test_sonic_aaa.py b/tests/unit/modules/network/sonic/test_sonic_aaa.py index de747c2c8..fb1296059 100644 --- a/tests/unit/modules/network/sonic/test_sonic_aaa.py +++ b/tests/unit/modules/network/sonic/test_sonic_aaa.py @@ -54,6 +54,20 @@ def test_sonic_aaa_merged_01(self): result = self.execute_module(changed=True) self.validate_config_requests() + def test_sonic_aaa_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_aaa_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_aaa_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_aaa_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + def test_sonic_aaa_deleted_01(self): set_module_args(self.fixture_data['deleted_01']['module_args']) self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_aaa_config']) diff --git a/tests/unit/modules/network/sonic/test_sonic_bgp_af.py b/tests/unit/modules/network/sonic/test_sonic_bgp_af.py index f3b13fc4a..1fa4dd612 100644 --- a/tests/unit/modules/network/sonic/test_sonic_bgp_af.py +++ b/tests/unit/modules/network/sonic/test_sonic_bgp_af.py @@ -80,3 +80,17 @@ def test_sonic_bgp_af_deleted_03(self): self.initialize_config_requests(self.fixture_data['deleted_03']['expected_config_requests']) result = self.execute_module(changed=True) self.validate_config_requests() + + def test_sonic_bgp_af_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_bgp_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_bgp_af_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_bgp_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_bgp_neighbors.py b/tests/unit/modules/network/sonic/test_sonic_bgp_neighbors.py index f08511a86..007728542 100644 --- a/tests/unit/modules/network/sonic/test_sonic_bgp_neighbors.py +++ b/tests/unit/modules/network/sonic/test_sonic_bgp_neighbors.py @@ -73,3 +73,17 @@ def test_sonic_bgp_neighbors_deleted_02(self): self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) result = self.execute_module(changed=True) self.validate_config_requests() + + def test_sonic_bgp_neighbors_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_bgp_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_bgp_neighbors_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_bgp_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_lag_interfaces.py b/tests/unit/modules/network/sonic/test_sonic_lag_interfaces.py index feb3257c7..6a78711d9 100644 --- a/tests/unit/modules/network/sonic/test_sonic_lag_interfaces.py +++ b/tests/unit/modules/network/sonic/test_sonic_lag_interfaces.py @@ -74,3 +74,17 @@ def test_sonic_lag_interfaces_deleted_03(self): self.initialize_config_requests(self.fixture_data['deleted_03']['expected_config_requests']) result = self.execute_module(changed=True) self.validate_config_requests() + + def test_sonic_lag_interfaces_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_lag_interfaces_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_lag_interfaces_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_lag_interfaces_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_ldap.py b/tests/unit/modules/network/sonic/test_sonic_ldap.py new file mode 100644 index 000000000..99852d3ea --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_ldap.py @@ -0,0 +1,90 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_ldap, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicLdapModule(TestSonicModule): + module = sonic_ldap + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ldap.ldap.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ldap.ldap.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_ldap.yaml') + + def setUp(self): + super(TestSonicLdapModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'standard' + + def tearDown(self): + super(TestSonicLdapModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + + def test_sonic_ldap_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ldap_merged_02(self): + set_module_args(self.fixture_data['merged_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_02']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['merged_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ldap_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ldap_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ldap_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ldap_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_ldap_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_mgmt_servers.py b/tests/unit/modules/network/sonic/test_sonic_mgmt_servers.py new file mode 100644 index 000000000..a5dbba110 --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_mgmt_servers.py @@ -0,0 +1,87 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_mgmt_servers, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicMgmtServersModule(TestSonicModule): + module = sonic_mgmt_servers + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mgmt_servers.mgmt_servers.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.mgmt_servers.mgmt_servers.edit_config" + ) + cls.mock_utils_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_mgmt_servers.yaml') + + def setUp(self): + super(TestSonicMgmtServersModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'native' + self.utils_edit_config = self.mock_utils_edit_config.start() + self.utils_edit_config.side_effect = self.facts_side_effect + + def tearDown(self): + super(TestSonicMgmtServersModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + self.mock_utils_edit_config.stop() + + def test_sonic_mgmt_servers_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_mgmt_servers_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_mgmt_servers_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_mgmt_servers_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_mgmt_servers_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_mgmt_servers_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_mgmt_servers_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_mgmt_servers_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_mgmt_servers_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_mgmt_servers_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_ospf_area.py b/tests/unit/modules/network/sonic/test_sonic_ospf_area.py new file mode 100644 index 000000000..4484a0835 --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_ospf_area.py @@ -0,0 +1,152 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_ospf_area +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicOspfAreaModule(TestSonicModule): + module = sonic_ospf_area + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospf_area.ospf_area.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospf_area.ospf_area.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.mock_bgp_utils_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils.edit_config" + ) + + cls.fixture_data = cls.load_fixtures('sonic_ospf_area.yaml') + + def setUp(self): + super(TestSonicOspfAreaModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.facts_edit_config.side_effect = self.facts_side_effect + + self.config_edit_config = self.mock_config_edit_config.start() + self.config_edit_config.side_effect = self.config_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'native' + + self.utils_edit_config = self.mock_bgp_utils_edit_config.start() + self.utils_edit_config.side_effect = self.facts_side_effect + + def tearDown(self): + super(TestSonicOspfAreaModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + self.mock_bgp_utils_edit_config.stop() + + def test_sonic_ospf_area_merged_keys(self): + test_case_name = "merged_keys" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_merged_01_all_settings(self): + test_case_name = "merged_01_all_settings" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_merged_add_minimum(self): + test_case_name = "merged_add_minimum" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_merge_changes(self): + test_case_name = "merge_changes" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_merge_no_changes(self): + test_case_name = "merge_no_changes" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_delete_areas(self): + test_case_name = "delete_areas" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_clear_subsections(self): + test_case_name = "clear_subsections" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_clear_everything(self): + test_case_name = "clear_everything" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_delete_attributes(self): + test_case_name = "delete_attributes" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_no_deletes(self): + test_case_name = "no_deletes" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_replaced(self): + test_case_name = "replaced" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() + + def test_sonic_ospf_area_overridden(self): + test_case_name = "overridden" + set_module_args(self.fixture_data[test_case_name]['module_args']) + self.initialize_facts_get_requests(self.fixture_data[test_case_name]['existing_config']) + self.initialize_config_requests(self.fixture_data[test_case_name]['expected_config_requests']) + result = self.execute_module(changed=self.fixture_data[test_case_name]['makes_changes']) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_ospfv2.py b/tests/unit/modules/network/sonic/test_sonic_ospfv2.py new file mode 100644 index 000000000..b652f575a --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_ospfv2.py @@ -0,0 +1,96 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_ospfv2, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicOspfv2Module(TestSonicModule): + module = sonic_ospfv2 + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospfv2.ospfv2.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospfv2.ospfv2.edit_config" + ) + cls.mock_bgp_utils_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_ospfv2.yaml') + + def setUp(self): + super(TestSonicOspfv2Module, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + self.utils_edit_config = self.mock_bgp_utils_edit_config.start() + + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + self.utils_edit_config.side_effect = self.facts_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'standard' + + def tearDown(self): + super(TestSonicOspfv2Module, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_bgp_utils_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + + def test_sonic_ospfv2_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_merged_02(self): + set_module_args(self.fixture_data['merged_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_02']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['merged_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_ospfv2_interfaces.py b/tests/unit/modules/network/sonic/test_sonic_ospfv2_interfaces.py new file mode 100644 index 000000000..c36f199c7 --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_ospfv2_interfaces.py @@ -0,0 +1,96 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_ospfv2_interfaces, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicOspfv2InterfacesModule(TestSonicModule): + module = sonic_ospfv2_interfaces + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospfv2_interfaces.ospfv2_interfaces.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ospfv2_interfaces.ospfv2_interfaces.edit_config" + ) + cls.mock_bgp_utils_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_ospfv2_interfaces.yaml') + + def setUp(self): + super(TestSonicOspfv2InterfacesModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + self.utils_edit_config = self.mock_bgp_utils_edit_config.start() + + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + self.utils_edit_config.side_effect = self.facts_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'standard' + + def tearDown(self): + super(TestSonicOspfv2InterfacesModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_bgp_utils_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + + def test_sonic_ospfv2_interfaces_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_interfaces_merged_02(self): + set_module_args(self.fixture_data['merged_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_02']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['merged_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_interfaces_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_interfaces_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_interfaces_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ospfv2_interfaces_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_ospfv2_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() diff --git a/tests/unit/modules/network/sonic/test_sonic_vrrp.py b/tests/unit/modules/network/sonic/test_sonic_vrrp.py new file mode 100644 index 000000000..80e25b756 --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_vrrp.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_vrrp, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicVrrpModule(TestSonicModule): + module = sonic_vrrp + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vrrp.vrrp.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vrrp.vrrp.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_vrrp.yaml') + + def setUp(self): + super(TestSonicVrrpModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'standard' + + def tearDown(self): + super(TestSonicVrrpModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + + def test_sonic_vrrp_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_vrrp_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_vrrp_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_vrrp_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_vrrp_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_vrrp_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_vrrp_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_vrrp_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_vrrp_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_vrrp_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests()