diff --git a/README.md b/README.md index a11a7450d..d34741a30 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,11 @@ Name | Description [cisco.nxos.nxos_vpc](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vpc_module.rst)|Manages global VPC configuration [cisco.nxos.nxos_vpc_interface](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vpc_interface_module.rst)|Manages interface VPC configuration [cisco.nxos.nxos_vrf](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_module.rst)|(deprecated, removed after 2026-07-25) Manages global VRF configuration. +[cisco.nxos.nxos_vrf_address_family](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_address_family_module.rst)|Resource module to configure VRF address family definitions. [cisco.nxos.nxos_vrf_af](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_af_module.rst)|Manages VRF AF. [cisco.nxos.nxos_vrf_global](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_global_module.rst)|Resource module to configure VRF definitions. [cisco.nxos.nxos_vrf_interface](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_interface_module.rst)|Manages interface specific VRF configuration. +[cisco.nxos.nxos_vrf_interfaces](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_interfaces_module.rst)|Resource module to configure VRF interfaces. [cisco.nxos.nxos_vrrp](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrrp_module.rst)|Manages VRRP configuration on NX-OS switches. [cisco.nxos.nxos_vsan](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vsan_module.rst)|Configuration of vsan for Cisco NXOS MDS Switches. [cisco.nxos.nxos_vtp_domain](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vtp_domain_module.rst)|Manages VTP domain configuration. diff --git a/changelogs/fragments/add_vrf_interfaces.yaml b/changelogs/fragments/add_vrf_interfaces.yaml new file mode 100644 index 000000000..3d77cbfd2 --- /dev/null +++ b/changelogs/fragments/add_vrf_interfaces.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Added nxos_vrf_interfaces resource module, that helps with configuration of vrfs within interface in favor of nxos_vrf_interface module. diff --git a/changelogs/fragments/lag_interfaces_error.yaml b/changelogs/fragments/lag_interfaces_error.yaml new file mode 100644 index 000000000..bb607bd58 --- /dev/null +++ b/changelogs/fragments/lag_interfaces_error.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - "lag_interfaces - Fix bug where lag interfaces was not erroring on command failure. (https://github.com/ansible-collections/cisco.nxos/pull/923)" diff --git a/changelogs/fragments/nxos_facts_cpu_utilization.yaml b/changelogs/fragments/nxos_facts_cpu_utilization.yaml new file mode 100644 index 000000000..fe93ca2d5 --- /dev/null +++ b/changelogs/fragments/nxos_facts_cpu_utilization.yaml @@ -0,0 +1,2 @@ +bugfixes: + - "Fixed hardware fact gathering failure for CPU utilization parsing on NX-OS 9.3(3) by handling both list and single value formats of onemin_percent" diff --git a/changelogs/fragments/nxos_feature.yaml b/changelogs/fragments/nxos_feature.yaml new file mode 100644 index 000000000..48398e965 --- /dev/null +++ b/changelogs/fragments/nxos_feature.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - Fixed the invalid feature name error for port-security by updating the feature mapping from `eth_port_sec` to `eth-port-sec`. diff --git a/changelogs/fragments/nxos_l2_interfaces.yaml b/changelogs/fragments/nxos_l2_interfaces.yaml new file mode 100644 index 000000000..e7b055781 --- /dev/null +++ b/changelogs/fragments/nxos_l2_interfaces.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - "nxos_l2_interfaces - Fixed handling of 'none' value in allowed_vlans to properly set trunk VLAN none" diff --git a/changelogs/fragments/nxos_telemetry_overridden.yaml b/changelogs/fragments/nxos_telemetry_overridden.yaml new file mode 100644 index 000000000..c9a366efc --- /dev/null +++ b/changelogs/fragments/nxos_telemetry_overridden.yaml @@ -0,0 +1,2 @@ +minor_changes: + - "nxos_telemetry - Added support for 'overridden' state to provide complete configuration override capabilities." diff --git a/changelogs/fragments/tests_fix.yaml b/changelogs/fragments/tests_fix.yaml new file mode 100644 index 000000000..bd6d86e79 --- /dev/null +++ b/changelogs/fragments/tests_fix.yaml @@ -0,0 +1,5 @@ +--- +bugfixes: + - Fixes mixed usage of f-string and format string in action plugin for consistency. +trivial: + - Added tests fixes for nxos_facts. diff --git a/changelogs/fragments/vrf_address_fam.yaml b/changelogs/fragments/vrf_address_fam.yaml new file mode 100644 index 000000000..df651596f --- /dev/null +++ b/changelogs/fragments/vrf_address_fam.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - "Add support for VRF address family via `vrf_address_family` resource module." diff --git a/docs/cisco.nxos.nxos_telemetry_module.rst b/docs/cisco.nxos.nxos_telemetry_module.rst index d1394ccf0..9d6e58535 100644 --- a/docs/cisco.nxos.nxos_telemetry_module.rst +++ b/docs/cisco.nxos.nxos_telemetry_module.rst @@ -565,6 +565,7 @@ Parameters
  • replaced
  • deleted
  • gathered
  • +
  • overridden
  • @@ -653,7 +654,7 @@ Examples # This action will replace telemetry configuration on the device with the # telemetry configuration defined in the playbook. - - name: Override Telemetry Configuration + - name: Replace Telemetry Configuration cisco.nxos.nxos_telemetry: config: certificate: @@ -674,6 +675,31 @@ Examples destination_group: 55 state: replaced + # Using overridden + # This action will override all telemetry configuration on the device with the + # telemetry configuration defined in the playbook. + + - name: Override Telemetry Configuration + cisco.nxos.nxos_telemetry: + config: + certificate: + key: /bootflash/server.key + hostname: localhost + compression: gzip + source_interface: Ethernet1/1 + vrf: management + destination_groups: + - id: 2 + destination: + ip: 192.168.0.2 + port: 50001 + protocol: gRPC + encoding: GPB + subscriptions: + - id: 5 + destination_group: 55 + state: overridden + Return Values diff --git a/docs/cisco.nxos.nxos_vrf_address_family_module.rst b/docs/cisco.nxos.nxos_vrf_address_family_module.rst new file mode 100644 index 000000000..053c290a2 --- /dev/null +++ b/docs/cisco.nxos.nxos_vrf_address_family_module.rst @@ -0,0 +1,1421 @@ +.. _cisco.nxos.nxos_vrf_address_family_module: + + +********************************** +cisco.nxos.nxos_vrf_address_family +********************************** + +**Resource module to configure VRF address family definitions.** + + +Version added: 9.3.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module provides declarative management of VRF definitions on Cisco NXOS. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ParameterChoices/DefaultsComments
    +
    + config + +
    + list + / elements=dictionary +
    +
    + +
    A list of device configurations for VRF address family.
    +
    +
    + address_families + +
    + list + / elements=dictionary +
    +
    + +
    Enable address family and enter its config mode - AFI/SAFI configuration
    +
    +
    + afi + +
    + string +
    +
    +
      Choices: +
    • ipv4
    • +
    • ipv6
    • +
    +
    +
    Address Family Identifier (AFI)
    +
    +
    + export + +
    + list + / elements=dictionary +
    +
    + +
    VRF export
    +
    +
    + map + +
    + string +
    +
    + +
    Route-map based VRF export
    +
    +
    + vrf + +
    + dictionary +
    +
    + +
    Virtual Router Context
    +
    +
    + allow_vpn + +
    + boolean +
    +
    +
      Choices: +
    • no
    • +
    • yes
    • +
    +
    +
    Allow re-importation of VPN imported routes
    +
    +
    + map_import + +
    + string +
    +
    + +
    Route-map based VRF import
    +
    +
    + max_prefix + +
    + integer +
    +
    + +
    Maximum prefix limit
    +
    +
    + import + +
    + list + / elements=dictionary +
    +
    + +
    VRF import
    +
    +
    + map + +
    + string +
    +
    + +
    Route-map based VRF export
    +
    +
    + vrf + +
    + dictionary +
    +
    + +
    Virtual Router Context
    +
    +
    + advertise_vpn + +
    + boolean +
    +
    +
      Choices: +
    • no
    • +
    • yes
    • +
    +
    +
    Allow leaked routes to be advertised to VPN
    +
    +
    + map_import + +
    + string +
    +
    + +
    Route-map based VRF import
    +
    +
    + max_prefix + +
    + integer +
    +
    + +
    Maximum prefix limit
    +
    +
    + maximum + +
    + dictionary +
    +
    + +
    Set a limit of routes
    +
    +
    + max_route_options + +
    + dictionary +
    +
    + +
    Configure the options for maximum routes
    +
    +
    + threshold + +
    + dictionary +
    +
    + +
    Configure threshold & its options
    +
    +
    + reinstall_threshold + +
    + integer +
    +
    + +
    Threshold value (%) at which to reinstall routes back to VRF
    +
    +
    + threshold_value + +
    + integer +
    +
    + +
    Threshold value (%) at which to generate a warning msg
    +
    +
    + warning_only + +
    + boolean +
    +
    +
      Choices: +
    • no
    • +
    • yes
    • +
    +
    +
    Configure only give a warning message if limit is exceeded
    +
    +
    + max_routes + +
    + integer +
    +
    + +
    Maximum number of routes allowed
    +
    +
    + route_target + +
    + list + / elements=dictionary +
    +
    + +
    Specify Target VPN Extended Communities
    +
    +
    + export + +
    + string +
    +
    + +
    Export Target-VPN community
    +
    +
    + import + +
    + string +
    +
    + +
    Import Target-VPN community
    +
    +
    + safi + +
    + string +
    +
    +
      Choices: +
    • multicast
    • +
    • unicast
    • +
    +
    +
    Address Family modifier
    +
    +
    + name + +
    + string + / required +
    +
    + +
    Name of the VRF.
    +
    +
    + running_config + +
    + string +
    +
    + +
    This option is used only with state parsed.
    +
    The value of this option should be the output received from the NX-OS device by executing the command show running-config | section ^vrf.
    +
    The state parsed reads the configuration from running_config option and transforms it into Ansible structured data as per the resource module's argspec and the value is then returned in the parsed key within the result.
    +
    +
    + state + +
    + string +
    +
    +
      Choices: +
    • parsed
    • +
    • gathered
    • +
    • deleted
    • +
    • purged
    • +
    • merged ←
    • +
    • replaced
    • +
    • rendered
    • +
    • overridden
    • +
    +
    +
    The state the configuration should be left in
    +
    The states rendered, gathered and parsed does not perform any change on the device.
    +
    The state rendered will transform the configuration in config option to platform specific CLI commands which will be returned in the rendered key within the result. For state rendered active connection to remote host is not required.
    +
    The state gathered will fetch the running configuration from device and transform it into structured data in the format as per the resource module argspec and the value is returned in the gathered key within the result.
    +
    The state parsed reads the configuration from running_config option and transforms it into JSON format as per the resource module parameters and the value is returned in the parsed key within the result. The value of running_config option should be the same format as the output of command show running-config | section ^vrf. connection to remote host is not required.
    +
    +
    + + +Notes +----- + +.. note:: + - Tested against NX-OS 9.3.6. + - This module works with connection ``network_cli`` and ``httpapi``. See https://docs.ansible.com/ansible/latest/network/user_guide/platform_nxos.html + + + +Examples +-------- + +.. code-block:: yaml + + # Using merged + + # Before state: + # ------------- + # + # nxos#show running-config | section ^vrf + + - name: Merge provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - export: "65512:200" + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - afi: ipv6 + safi: unicast + maximum: + max_routes: 1000 + route_target: + - import: "65512:200" + import: + - map: "22" + - vrf: + advertise_vpn: true + map_import: "44" + - vrf: + advertise_vpn: true + state: merged + + # Task Output: + # ------------ + + # before: {} + # commands: + # - vrf context VRF1 + # - address-family ipv4 unicast + # - maximum routes 500 60 reinstall 80 + # - route-target export 65512:200 + # - export map 22 + # - export vrf default map 44 allow-vpn + # - export vrf allow-vpn + # - address-family ipv6 unicast + # - maximum routes 1000 + # - route-target import 65512:200 + # - import map 22 + # - import vrf default map 44 advertise-vpn + # - import vrf advertise-vpn + # after: + # - address_families: + # - afi: ipv4 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 80 + # threshold_value: 60 + # max_routes: 500 + # route_target: + # - export: 65512:200 + # safi: unicast + # - afi: ipv6 + # import: + # - map: "22" + # - vrf: + # advertise_vpn: true + # map_import: "44" + # - vrf: + # advertise_vpn: true + # maximum: + # max_routes: 1000 + # route_target: + # - import: 65512:200 + # safi: unicast + # name: VRF1 + + # After state: + # ------------ + # + # nxos#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target export 65512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # maximum routes 500 60 reinstall 80 + # address-family ipv6 unicast + # route-target import 65512:200 + # import map 22 + # import vrf default map 44 advertise-vpn + # import vrf advertise-vpn + # maximum routes 1000 + + # Using deleted + + # Before state: + # ------------- + # + # nxos#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target import 64512:200 + # route-target export 64512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # maximum routes 900 22 reinstall 44 + + - name: Delete given vrf address family configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 64512:200 + export: + - map: "22" + maximum: + max_routes: 900 + max_route_options: + threshold: + threshold_value: 22 + reinstall_threshold: 44 + state: deleted + + # Task Output: + # ------------ + # + # before: + # - address_families: + # - afi: ipv4 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 44 + # threshold_value: 22 + # max_routes: 900 + # route_target: + # - import: "64512:200" + # - export: "64512:200" + # safi: unicast + # name: VRF1 + + # commands: + # - vrf context VRF1 + # - address-family ipv4 unicast + # - no maximum routes 900 22 reinstall 44 + # - no route-target import 64512:200 + # - no export map 22 + # after: + # - address_families: + # - afi: ipv4 + # export: + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # route_target: + # - export: "64512:200" + # safi: unicast + # name: VRF1 + + # Using purged + + # Before state: + # ------------- + # + # nxos#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target export 65512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # maximum routes 500 60 reinstall 80 + # address-family ipv6 unicast + # route-target import 65512:200 + # import map 22 + # import vrf default map 44 advertise-vpn + # import vrf advertise-vpn + # maximum routes 1000 + + - name: Purge the configuration of VRF address family + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + - afi: ipv6 + safi: unicast + state: purged + + # Task Output: + # ------------ + # + # before: + # - address_families: + # - afi: ipv4 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 80 + # threshold_value: 60 + # max_routes: 500 + # route_target: + # - export: 65512:200 + # safi: unicast + # - afi: ipv6 + # import: + # - map: "22" + # - vrf: + # advertise_vpn: true + # map_import: "44" + # - vrf: + # advertise_vpn: true + # maximum: + # max_routes: 1000 + # route_target: + # - import: 65512:200 + # safi: unicast + # name: VRF1 + # commands: + # - vrf context VRF1 + # - no address-family ipv4 unicast + # - no address-family ipv6 unicast + # after: {} + + + # Using overridden + + # Before state: + # ------------- + # + # nxos#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target import 64512:200 + # address-family ipv6 unicast + # route-target import 554832:500 + + - name: Override the provided configuration with the existing running configuration + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: overridden + + # Task Output: + # ------------ + # + # before: + # - address_families: + # - afi: ipv4 + # route_target: + # - import: 64512:200 + # safi: unicast + # - afi: ipv6 + # route_target: + # - import: 554832:500 + # safi: unicast + # name: VRF1 + # + # commands: + # - vrf context VRF1 + # - address-family ipv4 unicast + # - no route-target import 64512:200 + # - address-family ipv6 unicast + # - maximum routes 500 60 reinstall 80 + # - no route-target import 554832:500 + # - route-target export 65512:200 + # - export map 22 + # - export vrf default map 44 allow-vpn + # - export vrf allow-vpn + # - vrf context temp + # - address-family ipv4 unicast + # - maximum routes 1000 + # - route-target import 65512:200 + # - export map 26 + # - export vrf default map 46 allow-vpn + # after: + # - address_families: + # - afi: ipv4 + # safi: unicast + # - afi: ipv6 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 80 + # threshold_value: 60 + # max_routes: 500 + # route_target: + # - export: 65512:200 + # safi: unicast + # name: VRF1 + # - address_families: + # - afi: ipv4 + # export: + # - map: "26" + # - vrf: + # allow_vpn: true + # map_import: "46" + # maximum: + # max_routes: 1000 + # route_target: + # - import: 65512:200 + # safi: unicast + # name: temp + + # Using replaced + + # Before state: + # ------------- + # + # nxos# show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target import 64512:200 + # address-family ipv6 unicast + # route-target import 554832:500 + + - name: Replaced state for VRF configuration + cisco.nxos.nxos_vrf_global: + config: + vrfs: + - ip: + name_server: + address_list: + - 192.168.255.1 + route: + - destination: 192.168.255.1 + source: 0.0.0.0/0 + name: management + - name: temp + description: Test + ip: + auto_discard: true + domain_list: + - invalid.com + - example.com + domain_name: test.org + state: replaced + + # Task Output: + # ------------ + # + # before: + # - address_families: + # - afi: ipv4 + # route_target: + # - import: 64512:200 + # safi: unicast + # - afi: ipv6 + # route_target: + # - import: 554832:500 + # safi: unicast + # name: VRF1 + # commands: + # - vrf context VRF1 + # - address-family ipv4 unicast + # - no route-target import 64512:200 + # - address-family ipv6 unicast + # - maximum routes 500 60 reinstall 80 + # - no route-target import 554832:500 + # - route-target export 65512:200 + # - export map 22 + # - export vrf default map 44 allow-vpn + # - export vrf allow-vpn + # - vrf context temp + # - address-family ipv4 unicast + # - maximum routes 1000 + # - route-target import 65512:200 + # - export map 26 + # - export vrf default map 46 allow-vpn + # after: + # - address_families: + # - afi: ipv4 + # safi: unicast + # - afi: ipv6 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 80 + # threshold_value: 60 + # max_routes: 500 + # route_target: + # - export: 65512:200 + # safi: unicast + # name: VRF1 + # - address_families: + # - afi: ipv4 + # export: + # - map: "26" + # - vrf: + # allow_vpn: true + # map_import: "46" + # maximum: + # max_routes: 1000 + # route_target: + # - import: 65512:200 + # safi: unicast + # name: temp + # + # After state: + # ------------ + # router-ios#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv6 unicast + # route-target export 65512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # vrf context temp + # address-family ipv4 unicast + # route-target import 65512:200 + # export map 26 + # export vrf default map 46 allow-vpn + # maximum routes 1000 + + # Using gathered + + # Before state: + # ------------- + # + # nxos#show running-config | section ^vrf + # vrf context VRF1 + # address-family ipv4 unicast + # route-target export 65512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # maximum routes 500 60 reinstall 80 + # address-family ipv6 unicast + # route-target import 65512:200 + # import map 22 + # import vrf default map 44 advertise-vpn + # import vrf advertise-vpn + # maximum routes 1000 + + - name: Gathered state for VRF configuration + cisco.nxos.nxos_vrf_global: + config: + state: gathered + + # Task Output: + # ------------ + # + # gathered: + # - address_families: + # - afi: ipv4 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_route_options: + # threshold: + # reinstall_threshold: 80 + # threshold_value: 60 + # max_routes: 500 + # route_target: + # - export: 65512:200 + # safi: unicast + # - afi: ipv6 + # import: + # - map: "22" + # - vrf: + # advertise_vpn: true + # map_import: "44" + # - vrf: + # advertise_vpn: true + # maximum: + # max_routes: 1000 + # route_target: + # - import: 65512:200 + # safi: unicast + # name: VRF1 + + # Using rendered + + - name: Render provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: rendered + + # Task Output: + # ------------ + # + # commands: + # - vrf context VRF1 + # - address-family ipv6 unicast + # - maximum routes 500 60 reinstall 80 + # - route-target export 65512:200 + # - export map 22 + # - export vrf default map 44 allow-vpn + # - export vrf allow-vpn + # - vrf context temp + # - address-family ipv4 unicast + # - maximum routes 1000 + # - route-target import 65512:200 + # - export map 26 + # - export vrf default map 46 allow-vpn + + # Using Parsed + + # Parsed Config: + # ------------- + # vrf context VRF1 + # address-family ipv4 unicast + # route-target import 64512:200 + # route-target export 64512:200 + # export map 22 + # export vrf default map 44 allow-vpn + # export vrf allow-vpn + # maximum routes 900 22 reinstall 44 + # address-family ipv6 unicast + # route-target import 554832:500 + + - name: Parse the commands for provided configuration + register: result + cisco.nxos.nxos_vrf_address_family: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + + # Task Output: + # ------------ + # parsed: + # - name: VRF1 + # address_families: + # - afi: ipv4 + # safi: unicast + # route_target: + # - import: 64512:200 + # - export: 64512:200 + # export: + # - map: "22" + # - vrf: + # allow_vpn: true + # map_import: "44" + # - vrf: + # allow_vpn: true + # maximum: + # max_routes: 900 + # max_route_options: + # threshold: + # threshold_value: 22 + # reinstall_threshold: 44 + # - afi: ipv6 + # safi: unicast + # route_target: + # - import: 554832:500 + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyReturnedDescription
    +
    + after + +
    + list +
    +
    when changed +
    The resulting configuration after module execution.
    +
    +
    Sample:
    +
    This output will always be in the same format as the module argspec.
    +
    +
    + before + +
    + list +
    +
    when state is merged, replaced, overridden, deleted or purged +
    The configuration prior to the module execution.
    +
    +
    Sample:
    +
    This output will always be in the same format as the module argspec.
    +
    +
    + commands + +
    + list +
    +
    when state is merged, replaced, overridden, deleted or purged +
    The set of commands pushed to the remote device.
    +
    +
    Sample:
    +
    ['vrf context management', 'address-family ipv4 unicast', 'maximum routes 500 60 reinstall 80']
    +
    +
    + gathered + +
    + list +
    +
    when state is gathered +
    Facts about the network resource gathered from the remote device as structured data.
    +
    +
    Sample:
    +
    This output will always be in the same format as the module argspec.
    +
    +
    + parsed + +
    + list +
    +
    when state is parsed +
    The device native config provided in running_config option parsed into structured data as per module argspec.
    +
    +
    Sample:
    +
    This output will always be in the same format as the module argspec.
    +
    +
    + rendered + +
    + list +
    +
    when state is rendered +
    The provided configuration in the task rendered in device-native format (offline).
    +
    +
    Sample:
    +
    ['vrf context test1', 'address-family ipv6 unicast', 'route-target export 65512:200']
    +
    +

    + + +Status +------ + + +Authors +~~~~~~~ + +- Vinay Mulugund (@roverflow) diff --git a/galaxy.yml b/galaxy.yml index 494fdf171..c5d8ff2a8 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -10,4 +10,4 @@ readme: README.md repository: https://github.com/ansible-collections/cisco.nxos issues: https://github.com/ansible-collections/cisco.nxos/issues tags: [cisco, nxos, networking, nxapi, netconf] -version: 9.2.1 +version: 9.2.2-devel diff --git a/meta/runtime.yml b/meta/runtime.yml index 40dfbf1fc..156b23c75 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -205,3 +205,7 @@ plugin_routing: redirect: cisco.nxos.nxos_zone_zoneset vrf_global: redirect: cisco.nxos.nxos_vrf_global + vrf_address_family: + redirect: cisco.nxos.nxos_vrf_address_family + vrf_interfaces: + redirect: cisco.nxos.nxos_vrf_interfaces diff --git a/plugins/action/nxos.py b/plugins/action/nxos.py index e57c08aeb..c636c636d 100644 --- a/plugins/action/nxos.py +++ b/plugins/action/nxos.py @@ -60,8 +60,7 @@ def run(self, tmp=None, task_vars=None): "msg": ( f"Connection type must be fully qualified name for " f"network_cli connection type, got {self._play_context.connection}" - ) - % self._play_context.connection, + ), } conn = Connection(self._connection.socket_path) diff --git a/plugins/action/vrf_address_family.py b/plugins/action/vrf_address_family.py new file mode 120000 index 000000000..a69e27103 --- /dev/null +++ b/plugins/action/vrf_address_family.py @@ -0,0 +1 @@ +nxos.py \ No newline at end of file diff --git a/plugins/action/vrf_interfaces.py b/plugins/action/vrf_interfaces.py new file mode 120000 index 000000000..a69e27103 --- /dev/null +++ b/plugins/action/vrf_interfaces.py @@ -0,0 +1 @@ +nxos.py \ No newline at end of file diff --git a/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py b/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py index 7da72979b..b6b6c6ef3 100644 --- a/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py +++ b/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py @@ -108,7 +108,7 @@ class TelemetryArgs(object): # pylint: disable=R0903 "type": "dict", }, "state": { - "choices": ["merged", "replaced", "deleted", "gathered"], + "choices": ["merged", "replaced", "deleted", "gathered", "overridden"], "default": "merged", "type": "str", }, diff --git a/plugins/module_utils/network/nxos/argspec/vrf_address_family/__init__.py b/plugins/module_utils/network/nxos/argspec/vrf_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/argspec/vrf_address_family/vrf_address_family.py b/plugins/module_utils/network/nxos/argspec/vrf_address_family/vrf_address_family.py new file mode 100644 index 000000000..4dfec54ee --- /dev/null +++ b/plugins/module_utils/network/nxos/argspec/vrf_address_family/vrf_address_family.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the nxos_vrf_address_family module +""" + + +class Vrf_address_familyArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_vrf_address_family module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "address_families": { + "type": "list", + "elements": "dict", + "options": { + "afi": {"type": "str", "choices": ["ipv4", "ipv6"]}, + "safi": { + "type": "str", + "choices": ["multicast", "unicast"], + }, + "maximum": { + "type": "dict", + "options": { + "max_routes": {"type": "int"}, + "max_route_options": { + "type": "dict", + "mutually_exclusive": [ + ["warning_only", "threshold"], + ], + "options": { + "warning_only": {"type": "bool"}, + "threshold": { + "type": "dict", + "options": { + "threshold_value": {"type": "int"}, + "reinstall_threshold": { + "type": "int", + }, + }, + }, + }, + }, + }, + }, + "route_target": { + "type": "list", + "elements": "dict", + "mutually_exclusive": [["import", "export"]], + "options": { + "import": {"type": "str"}, + "export": {"type": "str"}, + }, + }, + "export": { + "type": "list", + "elements": "dict", + "mutually_exclusive": [["map", "vrf"]], + "options": { + "map": {"type": "str"}, + "vrf": { + "type": "dict", + "options": { + "max_prefix": {"type": "int"}, + "map_import": {"type": "str"}, + "allow_vpn": {"type": "bool"}, + }, + }, + }, + }, + "import": { + "type": "list", + "elements": "dict", + "mutually_exclusive": [["map", "vrf"]], + "options": { + "map": {"type": "str"}, + "vrf": { + "type": "dict", + "options": { + "max_prefix": {"type": "int"}, + "map_import": {"type": "str"}, + "advertise_vpn": {"type": "bool"}, + }, + }, + }, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "parsed", + "gathered", + "deleted", + "purged", + "merged", + "replaced", + "rendered", + "overridden", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/nxos/argspec/vrf_interfaces/__init__.py b/plugins/module_utils/network/nxos/argspec/vrf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/argspec/vrf_interfaces/vrf_interfaces.py b/plugins/module_utils/network/nxos/argspec/vrf_interfaces/vrf_interfaces.py new file mode 100644 index 000000000..9e4133535 --- /dev/null +++ b/plugins/module_utils/network/nxos/argspec/vrf_interfaces/vrf_interfaces.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the nxos_vrf_interfaces module +""" + + +class Vrf_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_vrf_interfaces module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "vrf_name": {"type": "str"}, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py b/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py index 913f8ecfb..11e8ec3d3 100644 --- a/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py +++ b/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py @@ -145,9 +145,13 @@ def expand_trunk_allowed_vlans(self, d): return None if "trunk" in d and d["trunk"]: if "allowed_vlans" in d["trunk"]: - allowed_vlans = vlan_range_to_list(d["trunk"]["allowed_vlans"]) - vlans_list = [str(line) for line in sorted(allowed_vlans)] - d["trunk"]["allowed_vlans"] = ",".join(vlans_list) + if d["trunk"]["allowed_vlans"]: + if d["trunk"]["allowed_vlans"].lower() == "none": + return + else: + allowed_vlans = vlan_range_to_list(d["trunk"]["allowed_vlans"]) + vlans_list = [str(line) for line in sorted(allowed_vlans)] + d["trunk"]["allowed_vlans"] = ",".join(vlans_list) def set_state(self, want, have): """Select the appropriate function based on the state provided @@ -347,5 +351,8 @@ def _reconstruct_commands(self, cmds): ) if match: data = match.groupdict() - unparsed = vlan_list_to_range(data["vlans"].split(",")) + if data["vlans"].lower() != "none": + unparsed = vlan_list_to_range(data["vlans"].split(",")) + else: + unparsed = "none" cmds[idx] = data["cmd"] + " " + unparsed diff --git a/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py index fc3202a7f..433a5a950 100644 --- a/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py @@ -86,6 +86,8 @@ def execute_module(self): err_str = item if err_str.lower().startswith("cannot add"): self._module.fail_json(msg=err_str) + elif err_str.lower().startswith("command failed"): + self._module.fail_json(msg=err_str) result["changed"] = True if self.state in self.ACTION_STATES: diff --git a/plugins/module_utils/network/nxos/config/telemetry/telemetry.py b/plugins/module_utils/network/nxos/config/telemetry/telemetry.py index da3f77439..9f3e109ed 100644 --- a/plugins/module_utils/network/nxos/config/telemetry/telemetry.py +++ b/plugins/module_utils/network/nxos/config/telemetry/telemetry.py @@ -82,8 +82,6 @@ def execute_module(self): warnings = list() state = self._module.params["state"] - if "overridden" in state: - self._module.fail_json(msg="State is invalid for this module.") # When state is 'deleted', the module_params should not contain data # under the 'config' key if "deleted" in state and self._module.params.get("config"): @@ -153,7 +151,7 @@ def set_state(self, want, have): # and does not require any processing using NxosCmdRef objects. if state == "deleted": return self._state_deleted(want, have) - elif state == "replaced": + elif state in ["replaced", "overridden"]: if want == have: return [] return self._state_replaced(want, have) diff --git a/plugins/module_utils/network/nxos/config/vrf_address_family/__init__.py b/plugins/module_utils/network/nxos/config/vrf_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py b/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py new file mode 100644 index 000000000..7a24cff7d --- /dev/null +++ b/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py @@ -0,0 +1,247 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_vrf_address_family config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.vrf_address_family import ( + Vrf_address_familyTemplate, +) + + +class Vrf_address_family(ResourceModule): + """ + The nxos_vrf_address_family config class + """ + + def __init__(self, module): + super(Vrf_address_family, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vrf_address_family", + tmplt=Vrf_address_familyTemplate(), + ) + self.parsers = [ + "maximum", + ] + self.list_parsers = [ + "route_target.import", + "route_target.export", + "export.map", + "export.vrf", + "import.map", + "import.vrf", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self._vrf_address_family_list_to_dict(self.want) + haved = self._vrf_address_family_list_to_dict(self.have) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = self._filter_have_to_want(haved, wantd) + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have, vrf=k) + + if self.state == "purged": + purge_list = wantd or haved + for k, item in iteritems(purge_list): + self.purge(k, item) + else: + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {}), vrf=k) + + def _compare(self, want, have, vrf): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Vrf_address_family network resource. + """ + begin = len(self.commands) + self._compare_vrf_afs(want=want, have=have) + if len(self.commands) != begin: + self.commands.insert(begin, f"vrf context {vrf}") + + def _compare_vrf_afs(self, want, have): + """Compare the VRF address families lists. + :params want: the want VRF dictionary + :params have: the have VRF dictionary + """ + waafs = want.get("address_families", {}) + haafs = have.get("address_families", {}) + + address_fam_list = [ + ("ipv4", "unicast"), + ("ipv6", "unicast"), + ("ipv4", "multicast"), + ("ipv6", "multicast"), + ] + + for item in address_fam_list: + begin = len(self.commands) + for afk, afv in iteritems(waafs): + if afv.get("afi", "") == item[0] and afv.get("safi", "") == item[1]: + self._compare_single_af(wantaf=afv, haveaf=haafs.pop(afk, {})) + for afk, afv in iteritems(haafs): + if afv.get("afi", "") == item[0] and afv.get("safi", "") == item[1]: + self._compare_single_af(wantaf={}, haveaf=afv) + if len(self.commands) != begin: + self.commands.insert( + begin, + self._tmplt.render( + {"afi": item[0], "safi": item[1]}, + "address_family", + False, + ), + ) + + def _compare_single_af(self, wantaf, haveaf): + """Compare a single address family. + :params want: the want address family dictionary + :params have: the have address family dictionary + """ + self.compare(parsers=self.parsers, want=wantaf, have=haveaf) + self._compare_af_lists(want=wantaf, have=haveaf) + + def _compare_af_lists(self, want, have): + """Compare single vrf af list items. + :params want: the want list item dictionary + :params have: the have list item dictionary + """ + + for attrib in self.list_parsers: + parser_split = attrib.split(".") + wdict = self._convert_to_dict(want.get(parser_split[0], []), parser_split[1]) + hdict = self._convert_to_dict(have.get(parser_split[0], []), parser_split[1]) + + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + self.addcmd(entry, attrib, False) + # remove remaining items in have for replaced + for entry in hdict.values(): + self.addcmd(entry, attrib, True) + + def purge(self, vrf, item): + """Purge the VRF configuration""" + self.commands.append(f"vrf context {vrf}") + for i, value in iteritems(item.get("address_families", {})): + self.commands.append( + self._tmplt.render( + {"afi": value.get("afi"), "safi": value.get("safi")}, + "address_family", + True, + ), + ) + + def _convert_to_dict(self, vrf_af_item: list, parser_item: str) -> dict: + """Convert to dict based on parser name. + + :params vrf_af_item: the vrf af item + :params parser_item: the parser name based on which it needs to be converted + :returns: A dictionary with items that have the key parser_item + """ + if not vrf_af_item: + return {} + + result = {} + for item in vrf_af_item: + if parser_item in item: + if parser_item == "vrf": + vrf_item = item.get("vrf", {}) + key = f"vrf_{vrf_item.get('max_prefix', 'noprefix')}_{vrf_item.get('map_import', 'nomap')}" + else: + key = item[parser_item] + result[key] = item + return result + + def _filter_have_to_want(self, haved, wantd): + if isinstance(haved, dict) and isinstance(wantd, dict): + filtered = { + k: self._filter_have_to_want(haved[k], wantd[k]) for k in haved if k in wantd + } + return {k: v for k, v in filtered.items() if v not in [None, {}, []]} + elif isinstance(haved, list) and isinstance(wantd, list): + filtered_list = [] + for h_item in haved: + if isinstance(h_item, dict): + for w_item in wantd: + filtered_item = self._filter_have_to_want(h_item, w_item) + if filtered_item not in [None, {}, []]: + filtered_list.append(filtered_item) + break + else: + if h_item in wantd: + filtered_list.append(h_item) + return filtered_list + else: + return haved if haved == wantd else None + + def _vrf_address_family_list_to_dict(self, vrf_af_list: list) -> dict: + """Convert a list of vrf_address_family dictionaries to a dictionary. + + :param vrf_af_list: A list of vrf_address_family dictionaries. + :type vrf_af_list: list + :rtype: dict + :returns: A dictionary of vrf_address_family dictionaries. + """ + + items = {} + for af_item in vrf_af_list: + name = af_item.get("name") + address_families = af_item.get("address_families", []) + item = { + "name": name, + "address_families": { + f"{name}_{af.get('afi')}_{af.get('safi')}": af for af in address_families + }, + } + + items[name] = item + return items diff --git a/plugins/module_utils/network/nxos/config/vrf_interfaces/__init__.py b/plugins/module_utils/network/nxos/config/vrf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/config/vrf_interfaces/vrf_interfaces.py b/plugins/module_utils/network/nxos/config/vrf_interfaces/vrf_interfaces.py new file mode 100644 index 000000000..88c465bb2 --- /dev/null +++ b/plugins/module_utils/network/nxos/config/vrf_interfaces/vrf_interfaces.py @@ -0,0 +1,103 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_vrf_interfaces config file. +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 its desired end-state is +created. +""" + + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.vrf_interfaces import ( + Vrf_interfacesTemplate, +) + + +class Vrf_interfaces(ResourceModule): + """ + The nxos_vrf_interfaces config class + """ + + def __init__(self, module): + super(Vrf_interfaces, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vrf_interfaces", + tmplt=Vrf_interfacesTemplate(), + ) + self.parsers = [ + "interface", + "vrf_name", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {entry["name"]: entry for entry in self.want} + haved = {entry["name"]: entry for entry in self.have} + + # Filter out mgmt0 interface + wantd = {k: v for k, v in wantd.items() if k != "mgmt0"} + haved = {k: v for k, v in haved.items() if k != "mgmt0"} + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Vrf_interfaces network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render(want or have, "interface", False)) diff --git a/plugins/module_utils/network/nxos/facts/facts.py b/plugins/module_utils/network/nxos/facts/facts.py index 4d1e38f92..cbb0ed831 100644 --- a/plugins/module_utils/network/nxos/facts/facts.py +++ b/plugins/module_utils/network/nxos/facts/facts.py @@ -111,9 +111,15 @@ from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vlans.vlans import ( VlansFacts, ) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_address_family.vrf_address_family import ( + Vrf_address_familyFacts, +) from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_global.vrf_global import ( Vrf_globalFacts, ) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_interfaces.vrf_interfaces import ( + Vrf_interfacesFacts, +) FACT_LEGACY_SUBSETS = dict( @@ -154,6 +160,8 @@ hostname=HostnameFacts, bgp_templates=Bgp_templatesFacts, vrf_global=Vrf_globalFacts, + vrf_address_family=Vrf_address_familyFacts, + vrf_interfaces=Vrf_interfacesFacts, ) MDS_FACT_RESOURCE_SUBSETS = dict( fc_interfaces=Fc_interfacesFacts, diff --git a/plugins/module_utils/network/nxos/facts/legacy/base.py b/plugins/module_utils/network/nxos/facts/legacy/base.py index aa39ea734..296539d79 100644 --- a/plugins/module_utils/network/nxos/facts/legacy/base.py +++ b/plugins/module_utils/network/nxos/facts/legacy/base.py @@ -169,6 +169,10 @@ def populate(self): self.facts["cpu_utilization"] = self.parse_cpu_utilization(data) def parse_cpu_utilization(self, data): + onemin = data.get("onemin_percent", ["0"]) + if not isinstance(onemin, list): + onemin = [str(onemin)] + onemin_value = onemin[0] return { "core": { "five_minutes": int(data.get("fivemin_percent", 0)), @@ -178,7 +182,7 @@ def parse_cpu_utilization(self, data): "five_seconds_interrupt": int( data.get("fivesec_intr_percent", 0), ), - "one_minute": int(data.get("onemin_percent", 0)), + "one_minute": int(onemin_value), }, } diff --git a/plugins/module_utils/network/nxos/facts/vrf_address_family/__init__.py b/plugins/module_utils/network/nxos/facts/vrf_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/facts/vrf_address_family/vrf_address_family.py b/plugins/module_utils/network/nxos/facts/vrf_address_family/vrf_address_family.py new file mode 100644 index 000000000..46b45ba7d --- /dev/null +++ b/plugins/module_utils/network/nxos/facts/vrf_address_family/vrf_address_family.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos vrf_address_family 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vrf_address_family.vrf_address_family import ( + Vrf_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.vrf_address_family import ( + Vrf_address_familyTemplate, +) + + +class Vrf_address_familyFacts(object): + """The nxos vrf_address_family facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Vrf_address_familyArgs.argument_spec + + def get_config(self, connection): + """Get the configuration from the device""" + + return connection.get("show running-config | section ^vrf") + + def _flatten_config(self, data): + """Flatten contexts in the vrf address family + running-config for easier parsing. + :param data: string running-config + :returns: flattened running config + """ + + dataLines = data.split("\n") + curData = "" + + for line in dataLines: + if "vrf context" in line: + curData = line + elif "address-family" in line: + dataLines[dataLines.index(line)] = curData + " " + line + + return "\n".join(dataLines) + + def _parse_vrf_af(self, data: dict): + """Parse the vrf address family data + :param data: dict of vrf address family data + :returns: argspec compliant list + """ + if not data: + return [] + + vrf_lists = list(data.values()) + for item in vrf_lists: + if "address_families" in item: + item["address_families"] = list(item["address_families"].values()) + return vrf_lists + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vrf_address_family network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + vrfObjs = [] + + if not data: + data = self.get_config(connection) + + flattened_data = self._flatten_config(data) + + # parse native config using the Vrf_address_family template + vrf_address_family_parser = Vrf_address_familyTemplate( + lines=flattened_data.splitlines(), + module=self._module, + ) + parsed_data = vrf_address_family_parser.parse() + vrfObjs = self._parse_vrf_af(parsed_data) + + ansible_facts["ansible_network_resources"].pop("vrf_address_family", None) + + params = utils.remove_empties( + vrf_address_family_parser.validate_config( + self.argument_spec, + {"config": vrfObjs}, + redact=True, + ), + ) + facts["vrf_address_family"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/nxos/facts/vrf_interfaces/__init__.py b/plugins/module_utils/network/nxos/facts/vrf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/nxos/facts/vrf_interfaces/vrf_interfaces.py b/plugins/module_utils/network/nxos/facts/vrf_interfaces/vrf_interfaces.py new file mode 100644 index 000000000..71a2e1f6f --- /dev/null +++ b/plugins/module_utils/network/nxos/facts/vrf_interfaces/vrf_interfaces.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos vrf_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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vrf_interfaces.vrf_interfaces import ( + Vrf_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.vrf_interfaces import ( + Vrf_interfacesTemplate, +) + + +class Vrf_interfacesFacts(object): + """The nxos vrf_interfaces facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Vrf_interfacesArgs.argument_spec + + def get_device_data(self, connection): + return connection.get_config(flags="interface") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vrf_interfaces network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_device_data(connection) + + # parse native config using the Vrf_interfaces template + vrf_interfaces_parser = Vrf_interfacesTemplate(lines=data.splitlines(), module=self._module) + objs = list(vrf_interfaces_parser.parse().values()) + + ansible_facts["ansible_network_resources"].pop("vrf_interfaces", None) + + params = utils.remove_empties( + vrf_interfaces_parser.validate_config( + self.argument_spec, + {"config": objs}, + redact=True, + ), + ) + + facts["vrf_interfaces"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/nxos/rm_templates/vrf_address_family.py b/plugins/module_utils/network/nxos/rm_templates/vrf_address_family.py new file mode 100644 index 000000000..63c59cb81 --- /dev/null +++ b/plugins/module_utils/network/nxos/rm_templates/vrf_address_family.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Vrf_address_family parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_maximum(maximum): + cmd = "maximum routes" + maxData = maximum.get("maximum") + if maxData.get("max_routes"): + cmd += f" {maxData['max_routes']}" + if maxData.get("max_route_options", {}).get("threshold", {}).get("threshold_value"): + threshold = maxData["max_route_options"]["threshold"] + cmd += f" {threshold['threshold_value']}" + if threshold.get("reinstall_threshold"): + cmd += f" reinstall {threshold['reinstall_threshold']}" + if maxData.get("max_route_options", {}).get("warning_only"): + cmd += " warning-only" + return cmd + + +def _tmplt_export_vrf(vrf): + cmd = "export vrf" + vrfData = vrf.get("vrf") + if vrfData.get("max_prefix") or vrfData.get("map_import"): + cmd += " default" + if vrfData.get("max_prefix"): + cmd += f" {vrfData['max_prefix']}" + if vrfData.get("map_import"): + cmd += f" map {vrfData['map_import']}" + if vrfData.get("allow_vpn"): + cmd += " allow-vpn" + return cmd + + +def _tmplt_import_vrf(vrf): + cmd = "import vrf" + vrfData = vrf.get("vrf") + if vrfData.get("max_prefix") or vrfData.get("map_import"): + cmd += " default" + if vrfData.get("max_prefix"): + cmd += f" {vrfData['max_prefix']}" + if vrfData.get("map_import"): + cmd += f" map {vrfData['map_import']}" + if vrfData.get("advertise_vpn"): + cmd += " advertise-vpn" + return cmd + + +class Vrf_address_familyTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Vrf_address_familyTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "address_family", + "getval": re.compile( + r""" + ^vrf\scontext\s(?P\S+)\s+ + (?P\s+address-family + \s(?P\S+)\s(?P\S+)) + $""", re.VERBOSE, + ), + "setval": "address-family {{ afi }} {{ safi }}", + "result": { + '{{ name }}': { + "name": "{{ name }}", + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + }, + }, + }, + }, + "shared": True, + }, + { + "name": "maximum", + "getval": re.compile( + r""" + \s+maximum\sroutes\s(?P\d+) + (\s(?P\d+))? + (\sreinstall\s(?P\d+))? + (\s((?Pwarning-only)))? + $""", re.VERBOSE, + ), + "setval": _tmplt_maximum, + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "maximum": { + "max_routes": "{{ max_routes }}", + "max_route_options": { + "warning_only": "{{ True if warning_only }}", + "threshold": { + "threshold_value": "{{ threshold_value }}", + "reinstall_threshold": "{{ reinstall }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "route_target.import", + "getval": re.compile( + r""" + \s+route-target\simport\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "route-target import {{ import }}", + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "route_target": [{ + "import": "{{ import }}", + }], + }, + }, + }, + }, + }, + { + "name": "route_target.export", + "getval": re.compile( + r""" + \s+route-target\sexport\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "route-target export {{ export }}", + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "route_target": [{ + "export": "{{ export }}", + }], + }, + }, + }, + }, + }, + { + "name": "export.map", + "getval": re.compile( + r""" + \s+export\smap\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "export map {{ map }}", + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "export": [{ + "map": "{{ export_map }}", + }], + }, + }, + }, + }, + }, + { + "name": "export.vrf", + "getval": re.compile( + r""" + \s+export\svrf + ((\sdefault) + (\s(?P\d+))? + (\smap\s(?P\S+))?)? + (\s(?Pallow-vpn))? + $""", re.VERBOSE, + ), + "setval": _tmplt_export_vrf, + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "export": [{ + "vrf": { + "allow_vpn": "{{ True if allow_vpn }}", + "max_prefix": "{{ max_prefix if max_prefix }}", + "map_import": "{{ map_import if map_import }}", + }, + }], + }, + }, + }, + }, + }, + { + "name": "import.map", + "getval": re.compile( + r""" + \s+import\smap\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "import map {{ map }}", + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "import": [{ + "map": "{{ import_map }}", + }], + }, + }, + }, + }, + }, + { + "name": "import.vrf", + "getval": re.compile( + r""" + \s+import\svrf + ((\sdefault) + (\s(?P\d+))? + (\smap\s(?P\S+))?)? + (\s(?Padvertise-vpn))? + $""", re.VERBOSE, + ), + "setval": _tmplt_import_vrf, + "result": { + '{{ name }}': { + "address_families": { + "{{ 'address_families_'+afi+'_'+safi }}": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "import": [{ + "vrf": { + "advertise_vpn": "{{ True if advertise_vpn }}", + "max_prefix": "{{ max_prefix if max_prefix }}", + "map_import": "{{ map_import if map_import }}", + }, + }], + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/plugins/module_utils/network/nxos/rm_templates/vrf_interfaces.py b/plugins/module_utils/network/nxos/rm_templates/vrf_interfaces.py new file mode 100644 index 000000000..cf0373e33 --- /dev/null +++ b/plugins/module_utils/network/nxos/rm_templates/vrf_interfaces.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Vrf_interfaces parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class Vrf_interfacesTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Vrf_interfacesTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + 'name': 'interface', + 'getval': re.compile( + r''' + ^interface\s + (?P\S+)$''', re.VERBOSE, + ), + 'setval': 'interface {{ name }}', + 'result': { + '{{ name }}': { + 'name': '{{ name }}', + }, + }, + 'shared': True, + }, + { + "name": "vrf_name", + "getval": re.compile( + r""" + \s+vrf\smember\s(?P\S+)$ + """, + re.VERBOSE, + ), + "setval": "vrf member {{ vrf_name }}", + "result": { + '{{ name }}': { + 'vrf_name': '{{ vrf_name }}', + }, + }, + }, + ] + # fmt: on diff --git a/plugins/modules/nxos_feature.py b/plugins/modules/nxos_feature.py index c9229c4d5..bf2ad69bd 100644 --- a/plugins/modules/nxos_feature.py +++ b/plugins/modules/nxos_feature.py @@ -234,7 +234,7 @@ def validate_feature(module, mode="show"): "tacacs+": "tacacs", "telnet": "telnetServer", "ethernet-link-oam": "elo", - "port-security": "eth_port_sec", + "port-security": "eth-port-sec", }, "config": { "nve": "nv overlay", @@ -249,7 +249,7 @@ def validate_feature(module, mode="show"): "tacacs": "tacacs+", "telnetServer": "telnet", "elo": "ethernet-link-oam", - "eth_port_sec": "port-security", + "eth-port-sec": "port-security", }, } diff --git a/plugins/modules/nxos_telemetry.py b/plugins/modules/nxos_telemetry.py index 89a58e2d2..4020c80e0 100644 --- a/plugins/modules/nxos_telemetry.py +++ b/plugins/modules/nxos_telemetry.py @@ -199,6 +199,7 @@ - replaced - deleted - gathered + - overridden default: merged """ @@ -267,7 +268,7 @@ # This action will replace telemetry configuration on the device with the # telemetry configuration defined in the playbook. -- name: Override Telemetry Configuration +- name: Replace Telemetry Configuration cisco.nxos.nxos_telemetry: config: certificate: @@ -287,6 +288,31 @@ - id: 5 destination_group: 55 state: replaced + +# Using overridden +# This action will override all telemetry configuration on the device with the +# telemetry configuration defined in the playbook. + +- name: Override Telemetry Configuration + cisco.nxos.nxos_telemetry: + config: + certificate: + key: /bootflash/server.key + hostname: localhost + compression: gzip + source_interface: Ethernet1/1 + vrf: management + destination_groups: + - id: 2 + destination: + ip: 192.168.0.2 + port: 50001 + protocol: gRPC + encoding: GPB + subscriptions: + - id: 5 + destination_group: 55 + state: overridden """ RETURN = """ before: diff --git a/plugins/modules/nxos_vrf_address_family.py b/plugins/modules/nxos_vrf_address_family.py new file mode 100644 index 000000000..60d2b9265 --- /dev/null +++ b/plugins/modules/nxos_vrf_address_family.py @@ -0,0 +1,913 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for nxos_vrf_address_family +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_vrf_address_family +short_description: Resource module to configure VRF address family definitions. +description: This module provides declarative management of VRF definitions on Cisco NXOS. +version_added: 9.3.0 +author: Vinay Mulugund (@roverflow) +notes: + - Tested against NX-OS 9.3.6. + - This module works with connection C(network_cli) and C(httpapi). + See U(https://docs.ansible.com/ansible/latest/network/user_guide/platform_nxos.html) +options: + config: + description: A list of device configurations for VRF address family. + type: list + elements: dict + suboptions: + name: + description: Name of the VRF. + type: str + required: true + address_families: + description: Enable address family and enter its config mode - AFI/SAFI configuration + type: list + elements: dict + suboptions: + afi: + description: Address Family Identifier (AFI) + type: str + choices: ["ipv4", "ipv6"] + safi: + description: Address Family modifier + type: str + choices: ["multicast", "unicast"] + maximum: + description: Set a limit of routes + type: dict + suboptions: + max_routes: + description: Maximum number of routes allowed + type: int + max_route_options: + description: Configure the options for maximum routes + type: dict + suboptions: + warning_only: + description: Configure only give a warning message if limit is exceeded + type: bool + threshold: + description: Configure threshold & its options + type: dict + suboptions: + threshold_value: + description: Threshold value (%) at which to generate a warning msg + type: int + reinstall_threshold: + description: Threshold value (%) at which to reinstall routes back to VRF + type: int + route_target: + description: Specify Target VPN Extended Communities + type: list + elements: dict + suboptions: + import: + description: Import Target-VPN community + type: str + export: + description: Export Target-VPN community + type: str + export: + description: VRF export + type: list + elements: dict + suboptions: + map: + description: Route-map based VRF export + type: str + vrf: + description: Virtual Router Context + type: dict + suboptions: + max_prefix: + description: Maximum prefix limit + type: int + map_import: + description: Route-map based VRF import + type: str + allow_vpn: + description: Allow re-importation of VPN imported routes + type: bool + import: + description: VRF import + type: list + elements: dict + suboptions: + map: + description: Route-map based VRF export + type: str + vrf: + description: Virtual Router Context + type: dict + suboptions: + max_prefix: + description: Maximum prefix limit + type: int + map_import: + description: Route-map based VRF import + type: str + advertise_vpn: + description: Allow leaked routes to be advertised to VPN + type: bool + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device by + executing the command B(show running-config | section ^vrf). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + state: + choices: [parsed, gathered, deleted, purged, merged, replaced, rendered, overridden] + default: merged + description: + - The state the configuration should be left in + - The states I(rendered), I(gathered) and I(parsed) does not perform any change + on the device. + - The state I(rendered) will transform the configuration in C(config) option to + platform specific CLI commands which will be returned in the I(rendered) key + within the result. For state I(rendered) active connection to remote host is + not required. + - The state I(gathered) will fetch the running configuration from device and transform + it into structured data in the format as per the resource module argspec and + the value is returned in the I(gathered) key within the result. + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into JSON format as per the resource module parameters and the + value is returned in the I(parsed) key within the result. The value of C(running_config) + option should be the same format as the output of command I(show running-config | section ^vrf). + connection to remote host is not required. + type: str +""" + +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# nxos#show running-config | section ^vrf + +- name: Merge provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - export: "65512:200" + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - afi: ipv6 + safi: unicast + maximum: + max_routes: 1000 + route_target: + - import: "65512:200" + import: + - map: "22" + - vrf: + advertise_vpn: true + map_import: "44" + - vrf: + advertise_vpn: true + state: merged + +# Task Output: +# ------------ + +# before: {} +# commands: +# - vrf context VRF1 +# - address-family ipv4 unicast +# - maximum routes 500 60 reinstall 80 +# - route-target export 65512:200 +# - export map 22 +# - export vrf default map 44 allow-vpn +# - export vrf allow-vpn +# - address-family ipv6 unicast +# - maximum routes 1000 +# - route-target import 65512:200 +# - import map 22 +# - import vrf default map 44 advertise-vpn +# - import vrf advertise-vpn +# after: +# - address_families: +# - afi: ipv4 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 80 +# threshold_value: 60 +# max_routes: 500 +# route_target: +# - export: 65512:200 +# safi: unicast +# - afi: ipv6 +# import: +# - map: "22" +# - vrf: +# advertise_vpn: true +# map_import: "44" +# - vrf: +# advertise_vpn: true +# maximum: +# max_routes: 1000 +# route_target: +# - import: 65512:200 +# safi: unicast +# name: VRF1 + +# After state: +# ------------ +# +# nxos#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target export 65512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# maximum routes 500 60 reinstall 80 +# address-family ipv6 unicast +# route-target import 65512:200 +# import map 22 +# import vrf default map 44 advertise-vpn +# import vrf advertise-vpn +# maximum routes 1000 + +# Using deleted + +# Before state: +# ------------- +# +# nxos#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target import 64512:200 +# route-target export 64512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# maximum routes 900 22 reinstall 44 + +- name: Delete given vrf address family configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 64512:200 + export: + - map: "22" + maximum: + max_routes: 900 + max_route_options: + threshold: + threshold_value: 22 + reinstall_threshold: 44 + state: deleted + +# Task Output: +# ------------ +# +# before: +# - address_families: +# - afi: ipv4 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 44 +# threshold_value: 22 +# max_routes: 900 +# route_target: +# - import: "64512:200" +# - export: "64512:200" +# safi: unicast +# name: VRF1 + +# commands: +# - vrf context VRF1 +# - address-family ipv4 unicast +# - no maximum routes 900 22 reinstall 44 +# - no route-target import 64512:200 +# - no export map 22 +# after: +# - address_families: +# - afi: ipv4 +# export: +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# route_target: +# - export: "64512:200" +# safi: unicast +# name: VRF1 + +# Using purged + +# Before state: +# ------------- +# +# nxos#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target export 65512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# maximum routes 500 60 reinstall 80 +# address-family ipv6 unicast +# route-target import 65512:200 +# import map 22 +# import vrf default map 44 advertise-vpn +# import vrf advertise-vpn +# maximum routes 1000 + +- name: Purge the configuration of VRF address family + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + - afi: ipv6 + safi: unicast + state: purged + +# Task Output: +# ------------ +# +# before: +# - address_families: +# - afi: ipv4 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 80 +# threshold_value: 60 +# max_routes: 500 +# route_target: +# - export: 65512:200 +# safi: unicast +# - afi: ipv6 +# import: +# - map: "22" +# - vrf: +# advertise_vpn: true +# map_import: "44" +# - vrf: +# advertise_vpn: true +# maximum: +# max_routes: 1000 +# route_target: +# - import: 65512:200 +# safi: unicast +# name: VRF1 +# commands: +# - vrf context VRF1 +# - no address-family ipv4 unicast +# - no address-family ipv6 unicast +# after: {} + + +# Using overridden + +# Before state: +# ------------- +# +# nxos#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target import 64512:200 +# address-family ipv6 unicast +# route-target import 554832:500 + +- name: Override the provided configuration with the existing running configuration + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: overridden + +# Task Output: +# ------------ +# +# before: +# - address_families: +# - afi: ipv4 +# route_target: +# - import: 64512:200 +# safi: unicast +# - afi: ipv6 +# route_target: +# - import: 554832:500 +# safi: unicast +# name: VRF1 +# +# commands: +# - vrf context VRF1 +# - address-family ipv4 unicast +# - no route-target import 64512:200 +# - address-family ipv6 unicast +# - maximum routes 500 60 reinstall 80 +# - no route-target import 554832:500 +# - route-target export 65512:200 +# - export map 22 +# - export vrf default map 44 allow-vpn +# - export vrf allow-vpn +# - vrf context temp +# - address-family ipv4 unicast +# - maximum routes 1000 +# - route-target import 65512:200 +# - export map 26 +# - export vrf default map 46 allow-vpn +# after: +# - address_families: +# - afi: ipv4 +# safi: unicast +# - afi: ipv6 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 80 +# threshold_value: 60 +# max_routes: 500 +# route_target: +# - export: 65512:200 +# safi: unicast +# name: VRF1 +# - address_families: +# - afi: ipv4 +# export: +# - map: "26" +# - vrf: +# allow_vpn: true +# map_import: "46" +# maximum: +# max_routes: 1000 +# route_target: +# - import: 65512:200 +# safi: unicast +# name: temp + +# Using replaced + +# Before state: +# ------------- +# +# nxos# show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target import 64512:200 +# address-family ipv6 unicast +# route-target import 554832:500 + +- name: Replaced state for VRF configuration + cisco.nxos.nxos_vrf_global: + config: + vrfs: + - ip: + name_server: + address_list: + - 192.168.255.1 + route: + - destination: 192.168.255.1 + source: 0.0.0.0/0 + name: management + - name: temp + description: Test + ip: + auto_discard: true + domain_list: + - invalid.com + - example.com + domain_name: test.org + state: replaced + +# Task Output: +# ------------ +# +# before: +# - address_families: +# - afi: ipv4 +# route_target: +# - import: 64512:200 +# safi: unicast +# - afi: ipv6 +# route_target: +# - import: 554832:500 +# safi: unicast +# name: VRF1 +# commands: +# - vrf context VRF1 +# - address-family ipv4 unicast +# - no route-target import 64512:200 +# - address-family ipv6 unicast +# - maximum routes 500 60 reinstall 80 +# - no route-target import 554832:500 +# - route-target export 65512:200 +# - export map 22 +# - export vrf default map 44 allow-vpn +# - export vrf allow-vpn +# - vrf context temp +# - address-family ipv4 unicast +# - maximum routes 1000 +# - route-target import 65512:200 +# - export map 26 +# - export vrf default map 46 allow-vpn +# after: +# - address_families: +# - afi: ipv4 +# safi: unicast +# - afi: ipv6 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 80 +# threshold_value: 60 +# max_routes: 500 +# route_target: +# - export: 65512:200 +# safi: unicast +# name: VRF1 +# - address_families: +# - afi: ipv4 +# export: +# - map: "26" +# - vrf: +# allow_vpn: true +# map_import: "46" +# maximum: +# max_routes: 1000 +# route_target: +# - import: 65512:200 +# safi: unicast +# name: temp +# +# After state: +# ------------ +# router-ios#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv6 unicast +# route-target export 65512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# vrf context temp +# address-family ipv4 unicast +# route-target import 65512:200 +# export map 26 +# export vrf default map 46 allow-vpn +# maximum routes 1000 + +# Using gathered + +# Before state: +# ------------- +# +# nxos#show running-config | section ^vrf +# vrf context VRF1 +# address-family ipv4 unicast +# route-target export 65512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# maximum routes 500 60 reinstall 80 +# address-family ipv6 unicast +# route-target import 65512:200 +# import map 22 +# import vrf default map 44 advertise-vpn +# import vrf advertise-vpn +# maximum routes 1000 + +- name: Gathered state for VRF configuration + cisco.nxos.nxos_vrf_global: + config: + state: gathered + +# Task Output: +# ------------ +# +# gathered: +# - address_families: +# - afi: ipv4 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_route_options: +# threshold: +# reinstall_threshold: 80 +# threshold_value: 60 +# max_routes: 500 +# route_target: +# - export: 65512:200 +# safi: unicast +# - afi: ipv6 +# import: +# - map: "22" +# - vrf: +# advertise_vpn: true +# map_import: "44" +# - vrf: +# advertise_vpn: true +# maximum: +# max_routes: 1000 +# route_target: +# - import: 65512:200 +# safi: unicast +# name: VRF1 + +# Using rendered + +- name: Render provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: rendered + +# Task Output: +# ------------ +# +# commands: +# - vrf context VRF1 +# - address-family ipv6 unicast +# - maximum routes 500 60 reinstall 80 +# - route-target export 65512:200 +# - export map 22 +# - export vrf default map 44 allow-vpn +# - export vrf allow-vpn +# - vrf context temp +# - address-family ipv4 unicast +# - maximum routes 1000 +# - route-target import 65512:200 +# - export map 26 +# - export vrf default map 46 allow-vpn + +# Using Parsed + +# Parsed Config: +# ------------- +# vrf context VRF1 +# address-family ipv4 unicast +# route-target import 64512:200 +# route-target export 64512:200 +# export map 22 +# export vrf default map 44 allow-vpn +# export vrf allow-vpn +# maximum routes 900 22 reinstall 44 +# address-family ipv6 unicast +# route-target import 554832:500 + +- name: Parse the commands for provided configuration + register: result + cisco.nxos.nxos_vrf_address_family: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +# Task Output: +# ------------ +# parsed: +# - name: VRF1 +# address_families: +# - afi: ipv4 +# safi: unicast +# route_target: +# - import: 64512:200 +# - export: 64512:200 +# export: +# - map: "22" +# - vrf: +# allow_vpn: true +# map_import: "44" +# - vrf: +# allow_vpn: true +# maximum: +# max_routes: 900 +# max_route_options: +# threshold: +# threshold_value: 22 +# reinstall_threshold: 44 +# - afi: ipv6 +# safi: unicast +# route_target: +# - import: 554832:500 +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: list + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: + - vrf context management + - address-family ipv4 unicast + - maximum routes 500 60 reinstall 80 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - vrf context test1 + - address-family ipv6 unicast + - route-target export 65512:200 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vrf_address_family.vrf_address_family import ( + Vrf_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.vrf_address_family.vrf_address_family import ( + Vrf_address_family, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vrf_address_familyArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Vrf_address_family(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/nxos_vrf_interfaces.py b/plugins/modules/nxos_vrf_interfaces.py new file mode 100644 index 000000000..47452dc5a --- /dev/null +++ b/plugins/modules/nxos_vrf_interfaces.py @@ -0,0 +1,553 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for nxos_vrf_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_vrf_interfaces +short_description: Resource module to configure VRF interfaces. +description: This module configures and manages the VRF configuration on interfaces on NX-OS platforms. +version_added: 1.0.0 +author: Ruchi Pakhle (@Ruchip16) +notes: + - Tested against Cisco NX-OS. + - This module works with connection C(network_cli). +options: + config: + description: A list of interface VRF configurations. + type: list + elements: dict + suboptions: + name: + description: + - Full name of the interface excluding any logical unit number, + i.e. Ethernet1/1. + type: str + required: true + vrf_name: + description: + - Name of the VRF to be configured on the interface. + - When configured, applies 'vrf member ' under the interface. + type: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS + device by executing the command B(show running-config interface). + - The state I(parsed) reads the configuration from C(running_config) + option and transforms it into Ansible structured data as per the + resource module's argspec and the value is then returned in the + I(parsed) key within the result. + type: str + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged +""" + +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no switchport +# interface Ethernet1/2 +# description test +# no switchport +# no shutdown +# interface Ethernet1/3 +# interface Ethernet1/4 +# no switchport +# speed 1000 +# no shutdown + +- name: Merge provided configuration with device configuration + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/3 + - name:Ethernet1/4 + state: merged + +# Task Output: +# ------------ +# +# before: +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# +# commands: +# - interface Ethernet1/2 +# - vrf member test +# +# after: +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# vrf_name: "test2" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" + +# After state: +# ------------ +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no ip address +# interface Ethernet1/2 +# vrf member test +# no ip address +# shutdown +# negotiation auto +# interface Ethernet1/3 +# no ip address +# negotiation auto +# interfaceEthernet1/4 +# no ip address +# shutdown +# negotiation auto + +# Using overridden + +# Before state: +# ------------- +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no ip address +# interface Ethernet1/1 +# ip address dhcp +# negotiation auto +# interface Ethernet1/2 +# vrf member vrf_B +# no ip address +# shutdown +# negotiation auto +# interface Ethernet1/3 +# no ip address +# negotiation auto +# interface Ethernet1/4 +# no ip address +# shutdown +# negotiation auto + +- name: Override device configuration with provided configuration + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + - name: Ethernet1/3 + - name: Ethernet1/4 + state: overridden + +# Task Output: +# ------------ +# +# before: +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# vrf_name: "vrf_B" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# +# commands: +# - interface Ethernet1/2 +# - no vrf member vrf_B +# +# after: +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" + +# After state: +# ------------ +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no ip address +# interface Ethernet1/2 +# no ip address +# shutdown +# negotiation auto +# interface Ethernet1/3 +# no ip address +# negotiation auto +# interface Ethernet1/4 +# no ip address +# shutdown +# negotiation auto + +# Using gathered + +# Before state: +# ------------- +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no ip address +# interface Ethernet1/2 +# vrf member vrf_B +# no ip address +# shutdown +# negotiation auto +# interface Ethernet1/3 +# no ip address +# negotiation auto +# interfaceEthernet1/4 +# no ip address +# shutdown +# negotiation auto + +- name: Gather listed VRF interfaces + cisco.nxos.nxos_vrf_interfaces: + state: gathered + +# Task Output: +# ------------ +# +# gathered: +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# vrf_name: "vrf_B" +# - name: "Ethernet1/3" + +# Using rendered + +- name: Render VRF configuration + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/3 + - name: Ethernet1/4 + state: rendered + +# Task Output: +# ------------ +# +# rendered: +# - interface Ethernet1/2 +# - vrf member test + +# Using parsed + +# File: parsed.cfg +# --------------- +# +# interface Ethernet1/2 +# no switchport +# vrf member VRF1 +# interface Ethernet1/6 +# no switchport +# speed 1000 +# vrf member TEST_VRF + +- name: Parse configuration from device running config + cisco.nxos.nxos_vrf_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task Output: +# ------------ +# +# parsed: +# - name: "Ethernet1/2" +# vrf_name: "VRF1" +# - name: "Ethernet1/6" +# vrf_name: "TEST_VRF" + +# Using replaced + +# Before state: +# ------------- +# +# nxos#show running-config interface +# interface Ethernet1/1 +# no ip address +# interface Ethernet1/2 +# vrf member vrf_B +# no ip address +# shutdown +# interface Ethernet1/3 +# no ip address +# interfaceEthernet1/4 +# vrf member vrf_C +# no ip address +# shutdown + +- name: Replace device configuration of listed VRF interfaces with provided configuration + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + vrf_name: test + - name: Ethernet1/2 + vrf_name: vrf_E + state: replaced + +# Task Output: +# ------------ +# +# before: +# - name: "Ethernet1/1" +# vrf_name: "vrf_A" +# - name: "Ethernet1/2" +# vrf_name: "vrf_B" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# vrf_name: "vrf_C" +# +# commands: +# - interface Ethernet1/1 +# - no vrf member vrf_A +# - vrf member test +# - interface Ethernet1/2 +# - no vrf member vrf_B +# - vrf member vrf_E +# +# after: +# - name: "Ethernet1/1" +# vrf_name: "test" +# - name: "Ethernet1/2" +# vrf_name: "vrf_E" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# vrf_name: "vrf_C" + +# Using deleted + +# Before state: +# ------------- +# +# nxos#show running-config interface +# interface Ethernet1/1 +# vrf member vrf_A +# ip address dhcp +# interface Ethernet1/2 +# vrf member vrf_B +# no ip address +# shutdown +# interface Ethernet1/3 +# no ip address +# interfaceEthernet1/4 +# vrf member vrf_C +# no ip address +# shutdown + +- name: Delete VRF configuration of specified interfaces + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + state: deleted + +# Task Output: +# ------------ +# +# before: +# - name: "Ethernet1/1" +# vrf_name: "vrf_A" +# - name: "Ethernet1/2" +# vrf_name: "vrf_B" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# vrf_name: "vrf_C" +# +# commands: +# - interface Ethernet1/1 +# - no vrf member vrf_A +# - interface Ethernet1/2 +# - no vrf member vrf_B +# +# after: +# - name: "Ethernet1/1" +# - name: "Ethernet1/1" +# - name: "Ethernet1/2" +# - name: "Ethernet1/3" +# - name: "Ethernet1/4" +# vrf_name: "vrf_C" + +# After state: +# ------------ +# +# nxos#show running-config interface +# interface Ethernet1/1 +# ip address dhcp +# interface Ethernet1/2 +# no ip address +# shutdown +# interface Ethernet1/3 +# no ip address +# interfaceEthernet1/4 +# vrf member vrf_C +# no ip address +# shutdown +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) + type: list + sample: > + [ + { + "name": "Ethernet1/1" + }, + { + "name": "Ethernet1/2", + "vrf_name": "test" + }, + { + "name": "Ethernet1/3" + }, + { + "name": "Ethernet1/4" + } + ] + +after: + description: The resulting configuration after module execution. + returned: when changed + type: list + sample: > + [ + { + "name": "Ethernet1/1" + }, + { + "name": "Ethernet1/2", + "vrf_name": "test" + }, + { + "name": "Ethernet1/3" + }, + { + "name": "Ethernet1/4" + } + ] + +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) + type: list + sample: + - "interface Ethernet1/2" + - "vrf member test" + - "no vrf member vrf_B" + +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - "interface Ethernet1/1" + - "vrf member vrf_C" + - "interface Ethernet1/2" + - "vrf member test" + +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + [ + { + "name": "Ethernet1/1" + }, + { + "name": "Ethernet1/2", + "vrf_name": "vrf_B" + }, + { + "name": "Ethernet1/3" + }, + { + "name": "Ethernet1/4" + } + ] + +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + [ + { + "name": "Ethernet1/1", + "vrf_name": "vrf_C" + }, + { + "name": "Ethernet1/2", + "vrf_name": "test" + }, + { + "name": "Ethernet1/3" + }, + { + "name": "Ethernet1/4" + } + ] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vrf_interfaces.vrf_interfaces import ( + Vrf_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.vrf_interfaces.vrf_interfaces import ( + Vrf_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vrf_interfacesArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Vrf_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/nxos_facts/vars/main.yml b/tests/integration/targets/nxos_facts/vars/main.yml index f010eb0ac..65cf3ce18 100644 --- a/tests/integration/targets/nxos_facts/vars/main.yml +++ b/tests/integration/targets/nxos_facts/vars/main.yml @@ -28,4 +28,6 @@ available_network_resources: - static_routes - telemetry - vlans + - vrf_address_family - vrf_global + - vrf_interfaces diff --git a/tests/integration/targets/nxos_file_copy/tests/cli/input_validation.yaml b/tests/integration/targets/nxos_file_copy/tests/cli/input_validation.yaml index bcda7b4fa..e05edc3fb 100644 --- a/tests/integration/targets/nxos_file_copy/tests/cli/input_validation.yaml +++ b/tests/integration/targets/nxos_file_copy/tests/cli/input_validation.yaml @@ -21,7 +21,7 @@ - ansible.builtin.assert: that: - - result is search("argument 'file_pull_timeout' is of type .* and we were unable to convert to int") + - result.msg is ansible.builtin.search("argument 'file_pull_timeout' is of type .* and we were unable to convert to int", multiline=true) - name: Input validation - param should be type register: result diff --git a/tests/integration/targets/nxos_l2_interfaces/tests/common/_populate_config.yaml b/tests/integration/targets/nxos_l2_interfaces/tests/common/_populate_config.yaml index 7f2724c9f..9451733c3 100644 --- a/tests/integration/targets/nxos_l2_interfaces/tests/common/_populate_config.yaml +++ b/tests/integration/targets/nxos_l2_interfaces/tests/common/_populate_config.yaml @@ -17,3 +17,12 @@ parents: "interface {{ nxos_int2 }}" vars: ansible_connection: ansible.netcommon.network_cli + +- name: Set VLAN trunking properties with "none" + cisco.nxos.nxos_config: + lines: + - "switchport" + - "switchport trunk allowed vlan none" + parents: "interface {{ nxos_int3 }}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/nxos_l2_interfaces/tests/common/replaced.yaml b/tests/integration/targets/nxos_l2_interfaces/tests/common/replaced.yaml index c37061c05..3720d0a47 100644 --- a/tests/integration/targets/nxos_l2_interfaces/tests/common/replaced.yaml +++ b/tests/integration/targets/nxos_l2_interfaces/tests/common/replaced.yaml @@ -2,10 +2,11 @@ - ansible.builtin.debug: msg: Start nxos_l2_interfaces replaced integration tests connection={{ ansible_connection }} -- name: Set a fact for 'test_int1' and 'test_int2' +- name: Set a fact for 'test_int1','test_int2'name and 'test_int3' ansible.builtin.set_fact: test_int1: "{{ nxos_int1 }}" test_int2: "{{ nxos_int2 }}" + test_int3: "{{ nxos_int3 }}" - name: Setup1 ignore_errors: true @@ -13,6 +14,7 @@ lines: - "default interface {{ test_int1 }}" - "default interface {{ test_int2 }}" + - "default interface {{ test_int3 }}" - block: - name: Setup2 @@ -30,6 +32,13 @@ - "switchport trunk allowed vlan 25-27" parents: "interface {{ test_int2 }}" + - name: Setup4 + cisco.nxos.nxos_config: + lines: + - "switchport" + - "switchport trunk allowed vlan 100-200" + parents: "interface {{ test_int3 }}" + - name: Gather l2_interfaces facts cisco.nxos.nxos_facts: &id001 gather_subset: @@ -50,6 +59,10 @@ - name: "{{ test_int2 }}" trunk: allowed_vlans: 25-27 + + - name: "{{ test_int3 }}" + trunk: + allowed_vlans: none state: replaced - ansible.builtin.assert: @@ -60,7 +73,9 @@ - "'switchport trunk allowed vlan 10-12' in result.commands" - "'interface {{ test_int2 }}' in result.commands" - "'no switchport trunk native vlan' in result.commands" - - result.commands|length == 5 + - "'interface {{ test_int3 }}' in result.commands" + - "'switchport trunk allowed vlan none' in result.commands" + - result.commands|length == 7 - name: Gather l2_interfaces post facts cisco.nxos.nxos_facts: *id001 diff --git a/tests/integration/targets/nxos_vrf_address_family/defaults/main.yaml b/tests/integration/targets/nxos_vrf_address_family/defaults/main.yaml new file mode 100644 index 000000000..871ea460c --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "[^_].*" diff --git a/tests/integration/targets/nxos_vrf_address_family/meta/main.yaml b/tests/integration/targets/nxos_vrf_address_family/meta/main.yaml new file mode 100644 index 000000000..f504a6ab2 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_nxos_tests diff --git a/tests/integration/targets/nxos_vrf_address_family/tasks/cli.yaml b/tests/integration/targets/nxos_vrf_address_family/tasks/cli.yaml new file mode 100644 index 000000000..ed4cb02e2 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tasks/cli.yaml @@ -0,0 +1,21 @@ +--- +- name: Collect all CLI test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + delegate_to: localhost + +- name: Run test case (connection=ansible.netcommon.network_cli) + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/nxos_vrf_address_family/tasks/main.yml b/tests/integration/targets/nxos_vrf_address_family/tasks/main.yml new file mode 100644 index 000000000..3a624e256 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: Main task for vrf_global module + when: not nxos_skip_marked | default(false) + ansible.builtin.include_tasks: cli.yaml + tags: + - network_cli diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/_parsed.cfg b/tests/integration/targets/nxos_vrf_address_family/tests/common/_parsed.cfg new file mode 100644 index 000000000..0e511a286 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/_parsed.cfg @@ -0,0 +1,10 @@ +vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + route-target export 64512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + maximum routes 900 22 reinstall 44 + address-family ipv6 unicast + route-target import 554832:500 diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/_populate_config.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/_populate_config.yaml new file mode 100644 index 000000000..081e71cf0 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/_populate_config.yaml @@ -0,0 +1,22 @@ +--- +- name: Merge provided vrf configuration with device configuration + cisco.nxos.nxos_config: + lines: + - vrf context VRF1 + - address-family ipv4 unicast + - route-target import 64512:200 + - address-family ipv6 unicast + - route-target import 554832:500 + match: none + retries: 3 + delay: 20 + +- name: Merge another vrf + cisco.nxos.nxos_config: + lines: + - vrf context temp + match: none + +- name: Wait for 5 seconds + ansible.builtin.wait_for: + timeout: 5 diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/_remove_config.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/_remove_config.yaml new file mode 100644 index 000000000..91bc45def --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/_remove_config.yaml @@ -0,0 +1,24 @@ +--- +- name: Remove VRF global configurations + cisco.nxos.nxos_config: + lines: + - no vrf context VRF1 + ignore_errors: true + register: testvrf + +- name: Wait for 20 seconds if VRF removal was successful + ansible.builtin.wait_for: + timeout: 20 + when: not testvrf.failed + +- name: Remove VRF global configurations + cisco.nxos.nxos_config: + lines: + - no vrf context temp + ignore_errors: true + register: temp + +- name: Wait for 20 seconds if VRF removal was successful + ansible.builtin.wait_for: + timeout: 20 + when: not temp.failed diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/deleted.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/deleted.yaml new file mode 100644 index 000000000..c11ceb032 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/deleted.yaml @@ -0,0 +1,72 @@ +--- +- ansible.builtin.debug: + msg: Start Deleted integration state for nxos_vrf_address_family ansible_connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Merge config to be deleted with the existing running configuration + cisco.nxos.nxos_config: + lines: + - route-target import 64512:200 + - route-target export 64512:200 + - export map 22 + - export vrf default map 44 allow-vpn + - export vrf allow-vpn + - maximum routes 900 22 reinstall 44 + parents: + - vrf context VRF1 + - address-family ipv4 unicast + retries: 2 + delay: 10 + + - name: Delete given vrf address family configuration + register: result + cisco.nxos.nxos_vrf_address_family: &id001 + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 64512:200 + export: + - map: "22" + maximum: + max_routes: 900 + max_route_options: + threshold: + threshold_value: 22 + reinstall_threshold: 44 + state: deleted + + - ansible.builtin.assert: + that: + - result.changed == true + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - "{{ general['after'] | symmetric_difference(result['before']) | length == 0 }}" + + - name: Assert that after dicts are correctly generated + ansible.builtin.assert: + that: + - deleted['after'] == result['after'] + + - name: Delete provided VRF global (idempotent) + register: result + cisco.nxos.nxos_vrf_address_family: *id001 + + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result.changed == false + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/empty_config.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/empty_config.yaml new file mode 100644 index 000000000..d73e5633b --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/empty_config.yaml @@ -0,0 +1,68 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_address_family empty_config.yaml integration tests on connection={{ ansible_connection }} + +- name: Merged with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + config: + state: merged + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + config: + state: replaced + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + config: + state: overridden + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Rendered with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + config: + state: rendered + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- name: Parsed with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + running_config: + state: parsed + +- ansible.builtin.assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- name: Purged with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_address_family: + config: + state: purged + +- ansible.builtin.debug: + msg: END nxos_vrf_address_family empty_config integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/gathered.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/gathered.yaml new file mode 100644 index 000000000..bfbd78f54 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/gathered.yaml @@ -0,0 +1,22 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_address_family gathered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Gather the provided configuration with the existing running configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + state: gathered + + - name: Assert gathered state + ansible.builtin.assert: + that: + - result.changed == false + - gathered == result['gathered'] + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/merged.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/merged.yaml new file mode 100644 index 000000000..9b62c2c2d --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/merged.yaml @@ -0,0 +1,70 @@ +--- +- ansible.builtin.debug: + msg: START Merged nxos_vrf_address_family state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Merge provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: &id001 + config: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - export: "65512:200" + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - afi: ipv6 + safi: unicast + maximum: + max_routes: 1000 + route_target: + - import: "65512:200" + import: + - map: "22" + - vrf: + advertise_vpn: true + map_import: "44" + - vrf: + advertise_vpn: true + state: merged + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ result['commands'] | symmetric_difference(merged['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - result['before'] == {} + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - result['after'] == merged['after'] + + - name: Merge provided configuration with device configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_address_family: *id001 + + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/overridden.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/overridden.yaml new file mode 100644 index 000000000..945bf0d15 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/overridden.yaml @@ -0,0 +1,76 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_address_family overridden integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Override the provided configuration with the existing running configuration + cisco.nxos.nxos_vrf_address_family: &overridden + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: overridden + register: result + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['before'] == result['before'] }}" + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Idempotency check + cisco.nxos.nxos_vrf_address_family: *overridden + register: result + + - name: Assert that no changes were made + ansible.builtin.assert: + that: + - result['changed'] == false + - result.commands|length == 0 + + - name: Assert that before dict is correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/parsed.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/parsed.yaml new file mode 100644 index 000000000..4a27cadab --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/parsed.yaml @@ -0,0 +1,14 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_address_family parsed integration tests on connection={{ ansible_connection }} + +- name: Parse the commands for provided configuration + register: result + cisco.nxos.nxos_vrf_address_family: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- ansible.builtin.assert: + that: + - result.changed == false + - parsed == result['parsed'] diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/rendered.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/rendered.yaml new file mode 100644 index 000000000..1ea69367c --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/rendered.yaml @@ -0,0 +1,49 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_address_family rendered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Render provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_address_family: + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: rendered + + - ansible.builtin.assert: + that: + - result.changed == false + - result.rendered|symmetric_difference(rendered.commands) == [] diff --git a/tests/integration/targets/nxos_vrf_address_family/tests/common/replaced.yaml b/tests/integration/targets/nxos_vrf_address_family/tests/common/replaced.yaml new file mode 100644 index 000000000..df78114c7 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tests/common/replaced.yaml @@ -0,0 +1,69 @@ +--- +- ansible.builtin.debug: + msg: START replaced nxos_vrf_address_family state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + cisco.nxos.nxos_vrf_address_family: &id001 + config: + - name: VRF1 + address_families: + - afi: ipv6 + safi: unicast + route_target: + - export: 65512:200 + maximum: + max_routes: 500 + max_route_options: + threshold: + threshold_value: 60 + reinstall_threshold: 80 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + - name: temp + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 65512:200 + maximum: + max_routes: 1000 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + state: replaced + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dict is correctly generated + ansible.builtin.assert: + that: + - "{{ replaced['before'] == result['before'] }}" + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - replaced['after'] == result['after'] + + - name: Replaced provided VRF global configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_address_family: *id001 + - name: Assert that task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_address_family/vars/main.yaml b/tests/integration/targets/nxos_vrf_address_family/vars/main.yaml new file mode 100644 index 000000000..18b330a44 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/vars/main.yaml @@ -0,0 +1,270 @@ +--- +general: + after: + - address_families: + - afi: ipv4 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + maximum: + max_route_options: + threshold: + reinstall_threshold: 44 + threshold_value: 22 + max_routes: 900 + route_target: + - import: "64512:200" + - export: "64512:200" + safi: unicast + name: VRF1 +merged: + after: + - address_families: + - afi: ipv4 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + maximum: + max_route_options: + threshold: + reinstall_threshold: 80 + threshold_value: 60 + max_routes: 500 + route_target: + - export: 65512:200 + safi: unicast + - afi: ipv6 + import: + - map: "22" + - vrf: + advertise_vpn: true + map_import: "44" + - vrf: + advertise_vpn: true + maximum: + max_routes: 1000 + route_target: + - import: 65512:200 + safi: unicast + name: VRF1 + commands: + - vrf context VRF1 + - address-family ipv4 unicast + - maximum routes 500 60 reinstall 80 + - route-target export 65512:200 + - export map 22 + - export vrf default map 44 allow-vpn + - export vrf allow-vpn + - address-family ipv6 unicast + - maximum routes 1000 + - route-target import 65512:200 + - import map 22 + - import vrf default map 44 advertise-vpn + - import vrf advertise-vpn +deleted: + after: + - address_families: + - afi: ipv4 + export: + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + route_target: + - export: "64512:200" + safi: unicast + name: VRF1 + commands: + - vrf context VRF1 + - address-family ipv4 unicast + - no maximum routes 900 22 reinstall 44 + - no route-target import 64512:200 + - no export map 22 +gathered: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 64512:200 + - afi: ipv6 + safi: unicast + route_target: + - import: 554832:500 +parsed: + - name: VRF1 + address_families: + - afi: ipv4 + safi: unicast + route_target: + - import: 64512:200 + - export: 64512:200 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + maximum: + max_routes: 900 + max_route_options: + threshold: + threshold_value: 22 + reinstall_threshold: 44 + - afi: ipv6 + safi: unicast + route_target: + - import: 554832:500 +replaced: + commands: + - vrf context VRF1 + - address-family ipv4 unicast + - no route-target import 64512:200 + - address-family ipv6 unicast + - maximum routes 500 60 reinstall 80 + - no route-target import 554832:500 + - route-target export 65512:200 + - export map 22 + - export vrf default map 44 allow-vpn + - export vrf allow-vpn + - vrf context temp + - address-family ipv4 unicast + - maximum routes 1000 + - route-target import 65512:200 + - export map 26 + - export vrf default map 46 allow-vpn + before: + - address_families: + - afi: ipv4 + route_target: + - import: 64512:200 + safi: unicast + - afi: ipv6 + route_target: + - import: 554832:500 + safi: unicast + name: VRF1 + after: + - address_families: + - afi: ipv4 + safi: unicast + - afi: ipv6 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + maximum: + max_route_options: + threshold: + reinstall_threshold: 80 + threshold_value: 60 + max_routes: 500 + route_target: + - export: 65512:200 + safi: unicast + name: VRF1 + - address_families: + - afi: ipv4 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + maximum: + max_routes: 1000 + route_target: + - import: 65512:200 + safi: unicast + name: temp +overridden: + after: + - address_families: + - afi: ipv4 + safi: unicast + - afi: ipv6 + export: + - map: "22" + - vrf: + allow_vpn: true + map_import: "44" + - vrf: + allow_vpn: true + maximum: + max_route_options: + threshold: + reinstall_threshold: 80 + threshold_value: 60 + max_routes: 500 + route_target: + - export: 65512:200 + safi: unicast + name: VRF1 + - address_families: + - afi: ipv4 + export: + - map: "26" + - vrf: + allow_vpn: true + map_import: "46" + maximum: + max_routes: 1000 + route_target: + - import: 65512:200 + safi: unicast + name: temp + before: + - address_families: + - afi: ipv4 + route_target: + - import: 64512:200 + safi: unicast + - afi: ipv6 + route_target: + - import: 554832:500 + safi: unicast + name: VRF1 + commands: + - vrf context VRF1 + - address-family ipv4 unicast + - no route-target import 64512:200 + - address-family ipv6 unicast + - maximum routes 500 60 reinstall 80 + - no route-target import 554832:500 + - route-target export 65512:200 + - export map 22 + - export vrf default map 44 allow-vpn + - export vrf allow-vpn + - vrf context temp + - address-family ipv4 unicast + - maximum routes 1000 + - route-target import 65512:200 + - export map 26 + - export vrf default map 46 allow-vpn +rendered: + commands: + - vrf context VRF1 + - address-family ipv6 unicast + - maximum routes 500 60 reinstall 80 + - route-target export 65512:200 + - export map 22 + - export vrf default map 44 allow-vpn + - export vrf allow-vpn + - vrf context temp + - address-family ipv4 unicast + - maximum routes 1000 + - route-target import 65512:200 + - export map 26 + - export vrf default map 46 allow-vpn diff --git a/tests/integration/targets/nxos_vrf_interfaces/defaults/main.yaml b/tests/integration/targets/nxos_vrf_interfaces/defaults/main.yaml new file mode 100644 index 000000000..5f709c5aa --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/nxos_vrf_interfaces/meta/main.yml b/tests/integration/targets/nxos_vrf_interfaces/meta/main.yml new file mode 100644 index 000000000..f504a6ab2 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_nxos_tests diff --git a/tests/integration/targets/nxos_vrf_interfaces/tasks/cli.yaml b/tests/integration/targets/nxos_vrf_interfaces/tasks/cli.yaml new file mode 100644 index 000000000..d3d521f2a --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tasks/cli.yaml @@ -0,0 +1,31 @@ +--- +- name: Collect common test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + connection: local + register: test_cases + +- name: Collect CLI test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + connection: local + register: cli_cases + +- name: Set a fact for 'test_cases' + ansible.builtin.set_fact: + test_cases: + files: "{{ test_cases.files + cli_cases.files }}" + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test cases with connection network_cli + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/nxos_vrf_interfaces/tasks/main.yaml b/tests/integration/targets/nxos_vrf_interfaces/tasks/main.yaml new file mode 100644 index 000000000..c9e70304e --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tasks/main.yaml @@ -0,0 +1,12 @@ +--- +- name: Run the CLI and NX-API tests + block: + - name: Include the CLI tasks + ansible.builtin.include_tasks: cli.yaml + tags: + - cli + always: + - name: Include the NX-API tasks + ansible.builtin.include_tasks: nxapi.yaml + tags: + - nxapi diff --git a/tests/integration/targets/nxos_vrf_interfaces/tasks/nxapi.yaml b/tests/integration/targets/nxos_vrf_interfaces/tasks/nxapi.yaml new file mode 100644 index 000000000..5fec0f43b --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tasks/nxapi.yaml @@ -0,0 +1,31 @@ +--- +- name: Collect common test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + connection: local + register: test_cases + +- name: Collect NX-API test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/nxapi" + patterns: "{{ testcase }}.yaml" + connection: local + register: nxapi_cases + +- name: Set a fact for 'test_cases' + ansible.builtin.set_fact: + test_cases: + files: "{{ test_cases.files + nxapi_cases.files }}" + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test cases with connection httpapi + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + vars: + ansible_connection: ansible.netcommon.httpapi diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/_parsed.cfg b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_parsed.cfg new file mode 100644 index 000000000..5c20d514b --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_parsed.cfg @@ -0,0 +1,7 @@ +interface Ethernet1/2 + no switchport + vrf member VRF1 +interface Ethernet1/6 + no switchport + speed 1000 + vrf member TEST_VRF diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/_populate_config.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_populate_config.yaml new file mode 100644 index 000000000..c3dea6c15 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_populate_config.yaml @@ -0,0 +1,11 @@ +--- +- name: Merge provided configuration with device configuration + cisco.nxos.nxos_vrf_interfaces: + state: overridden + config: + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/6 + vrf_name: test2 + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/_remove_config.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_remove_config.yaml new file mode 100644 index 000000000..a76e3c679 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/_remove_config.yaml @@ -0,0 +1,9 @@ +--- +- name: Override existing running configuration + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/2 + - name: Ethernet1/6 + state: overridden + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/deleted.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/deleted.yaml new file mode 100644 index 000000000..432527332 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/deleted.yaml @@ -0,0 +1,53 @@ +--- +- ansible.builtin.debug: + msg: Start Deleted integration state for nxos_vrf_interfaces ansible_connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Delete provided VRF interfaces + register: result + cisco.nxos.nxos_vrf_interfaces: &id001 + config: + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/6 + vrf_name: test2 + state: deleted + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Delete provided VRF interfaces (idempotent) + register: result + cisco.nxos.nxos_vrf_interfaces: *id001 + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result.changed == false + + - ansible.builtin.include_tasks: _populate_config.yaml + + - name: Delete provided VRF interfaces without any configuration + register: result + cisco.nxos.nxos_vrf_interfaces: &id002 + state: deleted + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Delete provided VRF interfaces without any configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_interfaces: *id002 + - name: Assert that the delete task was idempotent + ansible.builtin.assert: + that: + - result.changed == false + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/empty_config.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/empty_config.yaml new file mode 100644 index 000000000..077b6d9dd --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/empty_config.yaml @@ -0,0 +1,61 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_interfaces empty_config integration tests on connection={{ ansible_connection }} + +- name: Merged with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_interfaces: + config: + state: merged + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_interfaces: + config: + state: replaced + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_interfaces: + config: + state: overridden + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Rendered with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_interfaces: + config: + state: rendered + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- name: Parsed with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.nxos.nxos_vrf_interfaces: + running_config: + state: parsed + +- ansible.builtin.assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- ansible.builtin.debug: + msg: END nxos_vrf_interfaces empty_config integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/gathered.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/gathered.yaml new file mode 100644 index 000000000..28d46f965 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/gathered.yaml @@ -0,0 +1,28 @@ +--- +- name: START nxos_vrf_interfaces gathered integration tests + ansible.builtin.debug: + msg: START nxos_vrf_interfaces gathered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Gather the provided configuration + register: result + cisco.nxos.nxos_vrf_interfaces: + config: + state: gathered + + - name: Assert + ansible.builtin.assert: + that: + - not result.changed + - > + {{ + result['gathered'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(gathered['config']) + | length == 0 + }} + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/merged.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/merged.yaml new file mode 100644 index 000000000..1f01ba06d --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/merged.yaml @@ -0,0 +1,55 @@ +--- +- ansible.builtin.debug: + msg: START Merged nxos_vrf_interfaces state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Merge provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_interfaces: &id001 + config: + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/6 + vrf_name: test2 + state: merged + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['before'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(merged['before']) + | length == 0 + }} + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['after'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(merged['after']) + | length == 0 + }} + + - name: Merge provided configuration with device configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_interfaces: *id001 + + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/overridden.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/overridden.yaml new file mode 100644 index 000000000..0816b2b13 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/overridden.yaml @@ -0,0 +1,55 @@ +--- +- ansible.builtin.debug: + msg: START Overridden nxos_vrf_interfaces state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Override provided configuration with device configuration + register: result + cisco.nxos.nxos_vrf_interfaces: &id001 + config: + - name: Ethernet1/2 + vrf_name: VRF8 + - name: Ethernet1/6 + vrf_name: VRF9 + state: overridden + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['before'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(overridden['before']) + | length == 0 + }} + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['after'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(overridden['after']) + | length == 0 + }} + + - name: Override provided configuration with device configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_interfaces: *id001 + + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/parsed.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/parsed.yaml new file mode 100644 index 000000000..ce0594a28 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/parsed.yaml @@ -0,0 +1,14 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_interfaces parsed integration tests on connection={{ ansible_connection }} + +- name: Parse externally provided route-policy configuration + register: result + cisco.nxos.nxos_vrf_interfaces: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- name: Assert that configuration was correctly parsed + ansible.builtin.assert: + that: + - parsed['after'] == result['parsed'] diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/rendered.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/rendered.yaml new file mode 100644 index 000000000..5812ce24c --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/rendered.yaml @@ -0,0 +1,20 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_interfaces rendered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- name: Render route-policy configuration + register: result + cisco.nxos.nxos_vrf_interfaces: + state: rendered + config: + - name: Ethernet1/2 + vrf_name: test + - name: Ethernet1/6 + vrf_name: test2 + +- name: Assert that correct set of commands were rendered + ansible.builtin.assert: + that: + - merged['commands'] == result['rendered'] diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/replaced.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/replaced.yaml new file mode 100644 index 000000000..ecad626cb --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/replaced.yaml @@ -0,0 +1,56 @@ +--- +- ansible.builtin.debug: + msg: START Replaced nxos_vrf_interfaces state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Replace provided VRF interfaces configuration + register: result + cisco.nxos.nxos_vrf_interfaces: &id001 + config: + - name: Ethernet1/2 + vrf_name: TEST_VRF2 + - name: Ethernet1/6 + vrf_name: TEST_VRF3 + state: replaced + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['before'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(replaced['before']) + | length == 0 + }} + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - > + {{ + result['after'] + | selectattr('name', 'in', 'Ethernet1/2,Ethernet1/6') + | symmetric_difference(replaced['after']) + | length == 0 + }} + + - name: Replace provided VRF interfaces configuration (idempotent) + register: result + cisco.nxos.nxos_vrf_interfaces: *id001 + + - name: Assert that task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/tests/common/rtt.yaml b/tests/integration/targets/nxos_vrf_interfaces/tests/common/rtt.yaml new file mode 100644 index 000000000..1f7e85dc8 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/tests/common/rtt.yaml @@ -0,0 +1,53 @@ +--- +- ansible.builtin.debug: + msg: START nxos_vrf_interfaces round trip integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Apply the provided configuration (base config) + register: base_config + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + - name: Ethernet1/6 + vrf_name: test + state: merged + + - name: Gather VRF interfaces facts + cisco.nxos.nxos_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - vrf_interfaces + + - name: Apply the provided configuration (config to be reverted) + register: result + cisco.nxos.nxos_vrf_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + vrf_name: test2 + - name: Ethernet1/6 + state: overridden + + - ansible.builtin.assert: + that: + - result.changed == true + - result.commands|symmetric_difference(rtt.commands_no_revert) == [] + + - name: Revert back to base configuration using facts round trip + register: revert + cisco.nxos.nxos_vrf_interfaces: + config: "{{ ansible_facts['network_resources']['vrf_interfaces'] }}" + state: overridden + + - ansible.builtin.assert: + that: + - revert.changed == true + - revert.commands|symmetric_difference(rtt.commands) == [] + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/nxos_vrf_interfaces/vars/main.yaml b/tests/integration/targets/nxos_vrf_interfaces/vars/main.yaml new file mode 100644 index 000000000..685fbec44 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_interfaces/vars/main.yaml @@ -0,0 +1,93 @@ +--- +gathered: + config: + - name: "Ethernet1/2" + vrf_name: "test" + - name: "Ethernet1/6" + vrf_name: "test2" + +merged: + commands: + - interface Ethernet1/2 + - vrf member test + - interface Ethernet1/6 + - vrf member test2 + before: + - name: "Ethernet1/2" + - name: "Ethernet1/6" + after: + - name: "Ethernet1/2" + vrf_name: "test" + - name: "Ethernet1/6" + vrf_name: "test2" + +overridden: + commands: + - interface Ethernet1/2 + - vrf member VRF8 + - interface Ethernet1/6 + - vrf member VRF9 + before: + - name: "Ethernet1/2" + vrf_name: "test" + - name: "Ethernet1/6" + vrf_name: "test2" + after: + - name: "Ethernet1/2" + vrf_name: "VRF8" + - name: "Ethernet1/6" + vrf_name: "VRF9" + +parsed: + after: + - name: "Ethernet1/2" + vrf_name: "VRF1" + - name: "Ethernet1/6" + vrf_name: "TEST_VRF" + +replaced: + commands: + - interface Ethernet1/2 + - vrf member TEST_VRF2 + - interface Ethernet1/6 + - vrf member TEST_VRF3 + before: + - name: "Ethernet1/2" + vrf_name: "test" + - name: "Ethernet1/6" + vrf_name: "test2" + after: + - name: "Ethernet1/2" + vrf_name: "TEST_VRF2" + - name: "Ethernet1/6" + vrf_name: "TEST_VRF3" + +deleted: + commands: + - interface Ethernet1/2 + - no vrf member test + - interface Ethernet1/6 + - no vrf member test2 + before: + - name: "Ethernet1/1" + - name: "Ethernet1/2" + vrf_name: "vrf_B" + - name: "Ethernet1/3" + - name: "Ethernet1/4" + after: + - name: "Ethernet1/1" + - name: "Ethernet1/2" + - name: "Ethernet1/3" + - name: "Ethernet1/4" + +rtt: + commands: + - interface Ethernet1/6 + - vrf member test + - interface Ethernet1/2 + - no vrf member test2 + commands_no_revert: + - interface Ethernet1/2 + - vrf member test2 + - interface Ethernet1/6 + - no vrf member test diff --git a/tests/unit/modules/network/nxos/fixtures/nxos_feature/show_feature.txt b/tests/unit/modules/network/nxos/fixtures/nxos_feature/show_feature.txt index e27c595cf..b55a2089a 100644 --- a/tests/unit/modules/network/nxos/fixtures/nxos_feature/show_feature.txt +++ b/tests/unit/modules/network/nxos/fixtures/nxos_feature/show_feature.txt @@ -2,3 +2,4 @@ Feature Name Instance State -------------------- -------- ----- nve 1 disabled ospf 1 enabled +eth-port-sec 1 enabled diff --git a/tests/unit/modules/network/nxos/test_nxos_feature.py b/tests/unit/modules/network/nxos/test_nxos_feature.py index e98fbce1e..5e9ee5d4f 100644 --- a/tests/unit/modules/network/nxos/test_nxos_feature.py +++ b/tests/unit/modules/network/nxos/test_nxos_feature.py @@ -93,6 +93,11 @@ def test_nxos_feature_disable(self): result = self.execute_module(changed=True) self.assertEqual(result["commands"], ["terminal dont-ask", "no feature ospf"]) + def test_nxos_feature_port_security_disable(self): + set_module_args(dict(feature="port-security", state="disabled")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["terminal dont-ask", "no feature port-security"]) + class TestNxosFeatureModuleMDS(TestNxosModule): module = nxos_feature @@ -171,3 +176,8 @@ def test_nxos_feature_disable_already_disabled(self): set_module_args(dict(feature="sftp-server", state="disabled")) result = self.execute_module(changed=False) self.assertEqual(result["commands"], []) + + def test_nxos_feature_port_security_enable(self): + set_module_args(dict(feature="port-security", state="enabled")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["terminal dont-ask", "feature port-security"]) diff --git a/tests/unit/modules/network/nxos/test_nxos_telemetry.py b/tests/unit/modules/network/nxos/test_nxos_telemetry.py index 2c8027443..3e64d93b0 100644 --- a/tests/unit/modules/network/nxos/test_nxos_telemetry.py +++ b/tests/unit/modules/network/nxos/test_nxos_telemetry.py @@ -1997,6 +1997,101 @@ def test_tms_names_idempotent(self): ) self.execute_module(changed=False, commands=[]) + def test_tms_overridden_n9k(self): + # Assumes feature telemetry is enabled + # Similar to replaced state: + # - Modify vrf global config, remove default all other global config. + # - destination-group 2 destination '192.168.0.1' idempotent + # - destination-group 2 destination '192.168.0.2' remove + # - remove all other destination-groups + # - Modify sensor-group 55 and delete all others + # - Modify subscription 7, add 10 and delete all others + self.execute_show_command.return_value = load_fixture( + "nxos_telemetry", + "N9K.cfg", + ) + self.get_platform_shortname.return_value = "N9K" + set_module_args( + { + "state": "overridden", + "config": { + "vrf": "blue", + "destination_groups": [ + { + "id": 2, + "destination": { + "ip": "192.168.0.1", + "port": 50001, + "protocol": "GRPC", + "encoding": "GPB", + }, + }, + ], + "sensor_groups": [ + { + "id": 55, + "data_source": "NX-API", + "path": { + "name": "sys/bgp", + "depth": 0, + "query_condition": "query_condition_xyz", + "filter_condition": "filter_condition_xyz", + }, + }, + ], + "subscriptions": [ + { + "id": 7, + "destination_group": 10, + "sensor_group": { + "id": 55, + "sample_interval": 1000, + }, + }, + { + "id": 10, + "destination_group": 2, + "sensor_group": { + "id": 55, + "sample_interval": 1000, + }, + }, + ], + }, + }, + ignore_provider_arg, + ) + self.execute_module( + changed=True, + commands=[ + "telemetry", + "no subscription 3", + "no subscription 5", + "no subscription 4", + "subscription 7", + "no snsr-grp 2 sample-interval 1000", + "no subscription 6", + "no sensor-group 56", + "no sensor-group 2", + "no destination-group 10", + "destination-group 2", + "no ip address 192.168.0.2 port 60001 protocol grpc encoding gpb", + "sensor-group 55", + "data-source NX-API", + "path sys/bgp depth 0 query-condition query_condition_xyz filter-condition filter_condition_xyz", + "subscription 10", + "dst-grp 2", + "snsr-grp 55 sample-interval 1000", + "subscription 7", + "snsr-grp 55 sample-interval 1000", + "no certificate /bootflash/server.key localhost", + "destination-profile", + "no use-compression gzip", + "no source-interface loopback55", + "use-vrf blue", + ], + ) + def build_args(data, type, state=None, check_mode=None): if state is None: diff --git a/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py b/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py new file mode 100644 index 000000000..14ae64554 --- /dev/null +++ b/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py @@ -0,0 +1,941 @@ +# (c) 2024, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.cisco.nxos.plugins.modules import nxos_vrf_address_family + +from .nxos_module import TestNxosModule, set_module_args + + +class TestNxosVrfAddressFamilyModule(TestNxosModule): + """Test the nxos_vrf_address_family module.""" + + module = nxos_vrf_address_family + + def setUp(self): + """Set up for nxos_vrf_address_family module tests.""" + super(TestNxosVrfAddressFamilyModule, self).setUp() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_address_family.vrf_address_family." + "Vrf_address_familyFacts.get_config", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestNxosVrfAddressFamilyModule, self).tearDown() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def test_nxos_vrf_address_fam_parsed(self): + """Test parsed.""" + set_module_args( + dict( + running_config=dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + route-target export 64512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + maximum routes 900 22 reinstall 44 + address-family ipv6 unicast + route-target import 554832:500 + """, + ), + state="parsed", + ), + ) + + parsed_item = [ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "import": "64512:200", + }, + { + "export": "64512:200", + }, + ], + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + "maximum": { + "max_routes": 900, + "max_route_options": { + "threshold": { + "threshold_value": 22, + "reinstall_threshold": 44, + }, + }, + }, + }, + { + "afi": "ipv6", + "safi": "unicast", + "route_target": [ + { + "import": "554832:500", + }, + ], + }, + ], + }, + ] + + result = self.execute_module(changed=False) + self.assertEqual(parsed_item, result["parsed"]) + + def test_vrf_af_merged(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + address-family ipv6 unicast + route-target import 554832:500 + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="merged", + ), + ) + commands = [ + "vrf context VRF1", + "address-family ipv4 unicast", + "maximum routes 500 60 reinstall 80", + "route-target export 65512:200", + "export map 22", + "export vrf default map 44 allow-vpn", + "export vrf allow-vpn", + "address-family ipv6 unicast", + "maximum routes 1000", + "route-target import 65512:200", + "import map 22", + "import vrf default map 44 advertise-vpn", + "import vrf advertise-vpn", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_merged_idempotent(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + route-target import 554832:500 + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="merged", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_overridden(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + route-target import 554832:500 + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "export": [ + { + "map": "22", + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + commands = [ + "vrf context VRF1", + "address-family ipv4 unicast", + "no maximum routes 500 60 reinstall 80", + "no route-target import 64512:200", + "no export vrf default map 44 allow-vpn", + "no export vrf allow-vpn", + "address-family ipv6 unicast", + "no route-target import 554832:500", + "no import map 22", + "no import vrf default map 44 advertise-vpn", + "no import vrf advertise-vpn", + "vrf context VRF2", + "address-family ipv4 unicast", + "maximum routes 500 60 reinstall 80", + "route-target export 65512:200", + "export map 22", + "export vrf default map 44 allow-vpn", + "export vrf allow-vpn", + "address-family ipv6 unicast", + "maximum routes 1000", + "route-target import 65512:200", + "import map 22", + "import vrf default map 44 advertise-vpn", + "import vrf advertise-vpn", + ] + result = self.execute_module(changed=True) + print(result["commands"]) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_overridden_idemp(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target export 65512:200 + export map 22 + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + vrf context VRF2 + address-family ipv4 unicast + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "export": [ + { + "map": "22", + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_deleted(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target export 65512:200 + export map 22 + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + vrf context VRF2 + address-family ipv4 unicast + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + commands = [ + "vrf context VRF2", + "address-family ipv4 unicast", + "no maximum routes 500 60 reinstall 80", + "no route-target export 65512:200", + "no export map 22", + "no export vrf default map 44 allow-vpn", + "no export vrf allow-vpn", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_replaced(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + vrf context VRF2 + address-family ipv4 unicast + route-target export 65512:200 + address-family ipv6 unicast + maximum routes 1000 + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv6", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="replaced", + ), + ) + commands = [ + "vrf context VRF1", + "address-family ipv4 unicast", + "no route-target export 65512:200", + "no export map 22", + "no export vrf default map 44 allow-vpn", + "address-family ipv6 unicast", + "maximum routes 500 60 reinstall 80", + "no route-target import 65512:200", + "route-target export 65512:200", + "export map 22", + "export vrf default map 44 allow-vpn", + "export vrf allow-vpn", + "vrf context VRF2", + "address-family ipv4 unicast", + "maximum routes 500 60 reinstall 80", + "export map 22", + "export vrf default map 44 allow-vpn", + "export vrf allow-vpn", + "address-family ipv6 unicast", + "no maximum routes 1000", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_replaced_idem(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv6 unicast + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + vrf context VRF2 + address-family ipv4 unicast + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv6", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="replaced", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/tests/unit/modules/network/nxos/test_nxos_vrf_interfaces.py b/tests/unit/modules/network/nxos/test_nxos_vrf_interfaces.py new file mode 100644 index 000000000..a9bb8860a --- /dev/null +++ b/tests/unit/modules/network/nxos/test_nxos_vrf_interfaces.py @@ -0,0 +1,333 @@ +# (c) 2024 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.cisco.nxos.plugins.modules import nxos_vrf_interfaces + +from .nxos_module import TestNxosModule, set_module_args + + +class TestNxosVrfInterfacesModule(TestNxosModule): + """Test the nxos_vrf_interfaces module.""" + + module = nxos_vrf_interfaces + + def setUp(self): + """Set up for nxos_vrf_interfaces module tests""" + super(TestNxosVrfInterfacesModule, self).setUp() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_interfaces.vrf_interfaces." + "Vrf_interfacesFacts.get_device_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestNxosVrfInterfacesModule, self).tearDown() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def test_nxos_vrf_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member test + interface Ethernet1/6 + no switchport + speed 1000 + vrf member test2 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "test"}, + {"name": "Ethernet1/6", "vrf_name": "test2"}, + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_nxos_vrf_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + interface Ethernet1/6 + no switchport + speed 1000 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "test"}, + {"name": "Ethernet1/6", "vrf_name": "test2"}, + ], + state="merged", + ), + ) + commands = [ + "interface Ethernet1/2", + "vrf member test", + "interface Ethernet1/6", + "vrf member test2", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_nxos_vrf_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + interface Ethernet1/6 + no switchport + speed 1000 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "VRF8"}, + {"name": "Ethernet1/6", "vrf_name": "VRF6"}, + ], + state="replaced", + ), + ) + commands = [ + "interface Ethernet1/2", + "vrf member VRF8", + "interface Ethernet1/6", + "vrf member VRF6", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_nxos_vrf_interfaces_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member test + interface Ethernet1/6 + no switchport + vrf member test2 + speed 1000 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2"}, + {"name": "Ethernet1/3"}, + {"name": "Ethernet1/6"}, + ], + state="replaced", + ), + ) + commands = [ + "interface Ethernet1/2", + "no vrf member test", + "interface Ethernet1/6", + "no vrf member test2", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_nxos_vrf_interfaces_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member VRF1 + interface Ethernet1/6 + no switchport + speed 1000 + vrf member TEST_VRF + + """, + ), + state="parsed", + ), + ) + parsed_list = [ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "VRF1"}, + {"name": "Ethernet1/6", "vrf_name": "TEST_VRF"}, + ] + result = self.execute_module(changed=False) + + self.assertEqual(result["parsed"], parsed_list) + + def test_nxos_vrf_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member VRF1 + interface Ethernet1/6 + no switchport + speed 1000 + vrf member TEST_VRF + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "test"}, + {"name": "Ethernet1/6", "vrf_name": "test_vrf"}, + ], + state="replaced", + ), + ) + commands = [ + "interface Ethernet1/2", + "vrf member test", + "interface Ethernet1/6", + "vrf member test_vrf", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_nxos_vrf_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member test_vrf1 + interface Ethernet1/6 + no switchport + speed 1000 + vrf member test_vrf2 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "test_vrf1"}, + {"name": "Ethernet1/6", "vrf_name": "test_vrf2"}, + ], + state="replaced", + ), + ) + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_nxos_vrf_interfaces_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member VRF1 + interface Ethernet1/6 + no switchport + speed 1000 + vrf member TEST_VRF + """, + ) + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "VRF1"}, + {"name": "Ethernet1/6", "vrf_name": "TEST_VRF"}, + ], + state="overridden", + ), + ) + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_nxos_vrf_interfaces_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + interface Ethernet1/1 + interface Ethernet1/2 + no switchport + vrf member VRF1 + interface Ethernet1/3 + interface Ethernet1/4 + interface Ethernet1/5 + interface Ethernet1/6 + no switchport + speed 1000 + vrf member TEST_VRF + """, + ) + set_module_args(dict(state="gathered")) + gathered_list = [ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "VRF1"}, + {"name": "Ethernet1/3"}, + {"name": "Ethernet1/4"}, + {"name": "Ethernet1/5"}, + {"name": "Ethernet1/6", "vrf_name": "TEST_VRF"}, + ] + result = self.execute_module(changed=False) + self.assertEqual(result["gathered"], gathered_list) + + def test_nxos_vrf_interfaces_rendered(self): + self.execute_show_command.return_value = None + set_module_args( + dict( + config=[ + {"name": "Ethernet1/1"}, + {"name": "Ethernet1/2", "vrf_name": "test"}, + {"name": "Ethernet1/6", "vrf_name": "test2"}, + ], + state="rendered", + ), + ) + commands = [ + "interface Ethernet1/2", + "vrf member test", + "interface Ethernet1/6", + "vrf member test2", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands))