diff --git a/README.md b/README.md index 6e8b10133..d34741a30 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ 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. 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_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/meta/runtime.yml b/meta/runtime.yml index a703ed5fc..156b23c75 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -205,5 +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/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/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/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/facts/facts.py b/plugins/module_utils/network/nxos/facts/facts.py index 7f28e15bc..cbb0ed831 100644 --- a/plugins/module_utils/network/nxos/facts/facts.py +++ b/plugins/module_utils/network/nxos/facts/facts.py @@ -111,6 +111,9 @@ 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, ) @@ -157,6 +160,7 @@ 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( 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/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/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/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..adc4c6075 --- /dev/null +++ b/tests/integration/targets/nxos_vrf_address_family/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: Main task for vrf_global module + 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/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))