diff --git a/README.md b/README.md index ae858f82f..a411c7ee0 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Name | Description [cisco.ios.ios_user](https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_user_module.rst)|Module to manage the aggregates of local users. [cisco.ios.ios_vlans](https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_vlans_module.rst)|Resource module to configure VLANs. [cisco.ios.ios_vrf](https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_vrf_module.rst)|Module to configure VRF definitions. +[cisco.ios.ios_vrf_global](https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_vrf_global_module.rst)|Resource module to configure global VRF definitions. [cisco.ios.ios_vxlan_vtep](https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_vxlan_vtep_module.rst)|Resource module to configure VXLAN VTEP interface. diff --git a/changelogs/fragments/1055_add_vrf_global_module.yaml b/changelogs/fragments/1055_add_vrf_global_module.yaml new file mode 100644 index 000000000..e0afc510f --- /dev/null +++ b/changelogs/fragments/1055_add_vrf_global_module.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Add ios_vrf_global resource module in favor of ios_vrf module (fixes - https://github.com/ansible-collections/cisco.ios/pull/1055) diff --git a/docs/cisco.ios.ios_vrf_global_module.rst b/docs/cisco.ios.ios_vrf_global_module.rst new file mode 100644 index 000000000..eb92b8d70 --- /dev/null +++ b/docs/cisco.ios.ios_vrf_global_module.rst @@ -0,0 +1,1253 @@ +.. _cisco.ios.ios_vrf_global_module: + + +************************ +cisco.ios.ios_vrf_global +************************ + +**Resource module to configure global VRF definitions.** + + +Version added: 8.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module provides declarative management of VRF definitions on Cisco IOS. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ dictionary +
+
+ +
A dictionary containing device configurations for VRF, including a list of VRF definitions.
+
+
+ vrfs + +
+ list + / elements=dictionary +
+
+ +
List of VRF definitions.
+
+
+ description + +
+ string +
+
+ +
VRF specific description
+
+
+ ipv4 + +
+ dictionary +
+
+ +
VRF IPv4 configuration
+
+
+ multicast + +
+ dictionary +
+
+ +
IPv4 multicast configuration
+
+
+ multitopology + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable multicast-specific topology
+
+
+ ipv6 + +
+ dictionary +
+
+ +
VRF IPv6 configuration
+
+
+ multicast + +
+ dictionary +
+
+ +
IPv6 multicast configuration
+
+
+ multitopology + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable multicast-specific topology
+
+
+ name + +
+ string + / required +
+
+ +
Name of the VRF.
+
+
+ rd + +
+ string +
+
+ +
Specify route distinguisher (RD).
+
+
+ route_target + +
+ dictionary +
+
+ +
Specify target VPN extended configurations.
+
+
+ both + +
+ string +
+
+ +
Both export and import target-VPN configuration.
+
+
+ export + +
+ string +
+
+ +
Export target-VPN configuration.
+
+
+ import_config + +
+ string +
+
+ +
Import target-VPN configuration.
+
+
+ vnet + +
+ dictionary +
+
+ +
Virtual networking configuration.
+
+
+ tag + +
+ integer +
+
+ +
Identifier used to tag packets associated with this VNET.
+
+
+ vpn + +
+ dictionary +
+
+ +
Configure vpn-id for the VRF as specified in RFC 2685.
+
+
+ id + +
+ string +
+
+ +
Configure vpn-id in RFC 2685 format.
+
+
+ running_config + +
+ string +
+
+ +
This option is used only with state parsed.
+
The value of this option should be the output received from the IOS 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
  • +
  • merged ←
  • +
  • replaced
  • +
  • rendered
  • +
  • overridden
  • +
  • purged
  • +
+
+
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.
+
The state deleted only removes the VRF attributes that this module manages and does not negate the VRF completely. Thereby, preserving address-family related configurations under VRF context.
+
The state purged removes all the VRF definitions from the target device. Use caution with this state.
+
Refer to examples for more details.
+
+
+ + +Notes +----- + +.. note:: + - Tested against Cisco IOS-XE version 17.3 on CML. + - This module works with connection ``network_cli``. See https://docs.ansible.com/ansible/latest/network/user_guide/platform_ios.html + - The module examples uses callback plugin (stdout_callback = yaml) to generate task output in yaml format. + + + +Examples +-------- + +.. code-block:: yaml + + # Using merged + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + + - name: Merge provided configuration with device configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.0:100" + import_config: "192.0.2.3:200" + vpn: + id: "2:45" + vnet: + tag: 200 + state: merged + + # Task output + # ------------- + # + # before: {} + # + # commands: + # - vrf definition VRF2 + # - description This is a test VRF for merged state + # - ipv4 multicast multitopology + # - ipv6 multicast multitopology + # - rd 2:3 + # - route-target export 192.0.2.0:100 + # - route-target import 192.0.2.3:200 + # - vnet tag 200 + # - vpn id 2:45 + # + # after: + # - name: VRF2 + # description: This is a test VRF for merged state + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "2:3" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.3:200" + # vnet: + # tag: 200 + # vpn: + # id: "2:45" + + # After state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vnet tag 200 + # description This is a test VRF for merged state + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 2:3 + # vpn id 2:45 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.3:200 + + # Using replaced + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vnet tag 200 + # description This is a test VRF for merged state + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 2:3 + # vpn id 2:45 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.3:200 + + - name: Replace the provided configuration with the existing running configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF7 + description: VRF7 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "7:8" + route_target: + export: "198.51.100.112:500" + import_config: "192.0.2.4:400" + vpn: + id: "5:45" + vnet: + tag: 300 + state: replaced + + # Task Output: + # ------------ + # + # before: + # - name: VRF2 + # description: This is a test VRF for merged state + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "2:3" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.3:200" + # vnet: + # tag: 200 + # vpn: + # id: "2:45" + # + # commands: + # - vrf definition VRF7 + # - description VRF7 description + # - ipv4 multicast multitopology + # - ipv6 multicast multitopology + # - rd 7:8 + # - route-target export 198.51.100.112:500 + # - route-target import 192.0.2.4:400 + # - vnet tag 300 + # - vpn id 5:45 + # + # after: + # - name: VRF2 + # description: This is a test VRF for merged state + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "2:3" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.3:200" + # vnet: + # tag: 200 + # vpn: + # id: "2:45 + # - name: VRF7 + # description: VRF7 description + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "7:8" + # route_target: + # export: "198.51.100.112:500" + # import_config: "192.0.2.4:400" + # vnet: + # tag: 300 + # vpn: + # id: "5:45" + # + # After state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vnet tag 200 + # description This is a test VRF for merged state + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 2:3 + # vpn id 2:45 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.3:200 + # vrf definition VRF7 + # vnet tag 300 + # description VRF7 description + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 7:8 + # route-target export 198.51.100.112:500 + # route-target import 192.0.2.4:400 + # vpn id 5:45 + + # Using Overridden + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vnet tag 200 + # description This is a test VRF for merged state + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 2:3 + # vpn id 2:45 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.3:200 + # vrf definition VRF7 + # vnet tag 300 + # description VRF7 description + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 7:8 + # route-target export 198.51.100.112:500 + # route-target import 192.0.2.4:400 + # vpn id 5:45 + + - name: Override the provided configuration with the existing running configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF6 + description: VRF6 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "6:7" + route_target: + export: "198.51.0.2:400" + import_config: "198.51.0.5:200" + vpn: + id: "4:5" + vnet: + tag: 500 + state: overridden + + # Task Output: + # ------------ + # + # before: + # - name: VRF2 + # description: This is a test VRF for merged state + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "2:3" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.3:200" + # vnet: + # tag: 200 + # vpn: + # id: "2:45 + # - name: VRF7 + # description: VRF7 description + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "7:8" + # route_target: + # export: "198.51.100.112:500" + # import_config: "192.0.2.4:400" + # vnet: + # tag: 300 + # vpn: + # id: "5:45" + # + # commands: + # - vrf definition VRF2 + # - no description This is a test VRF for merged state + # - no ipv4 multicast multitopology + # - no ipv6 multicast multitopology + # - no rd 2:3 + # - no route-target export 192.0.2.0:100 + # - no route-target import 192.0.2.3:200 + # - no vnet tag 200 + # - no vpn id 2:45 + # - vrf definition VRF7 + # - no description VRF7 description + # - no ipv4 multicast multitopology + # - no ipv6 multicast multitopology + # - no rd 7:8 + # - no route-target export 198.51.100.112:500 + # - no route-target import 192.0.2.4:400 + # - no vnet tag 300 + # - no vpn id 5:45 + # - vrf definition VRF6 + # - description VRF6 description + # - ipv4 multicast multitopology + # - ipv6 multicast multitopology + # - rd 6:7 + # - route-target export 198.51.0.2:400 + # - route-target import 198.51.0.5:200 + # - vnet tag 500 + # - vpn id 4:5 + # + # after: + # - name: VRF2 + # - name: VRF6 + # description: VRF6 description + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "6:7" + # route_target: + # export: "198.51.0.2:400" + # import_config: "198.51.0.5:200" + # vnet: + # tag: 500 + # vpn: + # id: "4:5 + # - name: VRF7 + + # After state: + # ------------ + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vnet tag 500 + # description VRF6 description + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 6:7 + # vpn id 4:5 + # route-target export 198.51.0.2:400 + # route-target import 198.51.0.5:200 + # vrf definition VRF7 + + # Using Deleted + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vnet tag 500 + # description VRF6 description + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 6:7 + # vpn id 4:5 + # route-target export 198.51.0.2:400 + # route-target import 198.51.0.5:200 + # vrf definition VRF7 + + - name: Delete the provided configuration when config is given + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + - name: VRF6 + - name: VRF7 + state: deleted + + # Task Output: + # ------------ + # + # before: + # - name: VRF2 + # - name: VRF6 + # description: VRF6 description + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "6:7" + # route_target: + # export: "198.51.0.2:400" + # import_config: "198.51.0.5:200" + # vnet: + # tag: 500 + # vpn: + # id: "4:5" + # - name: VRF7 + # + # commands: + # - vrf definition VRF2 + # - vrf definition VRF6 + # - no description VRF6 description + # - no ipv4 multicast multitopology + # - no ipv6 multicast multitopology + # - no rd 6:7 + # - no route-target export 198.51.0.2:400 + # - no route-target import 198.51.0.5:200 + # - no vnet tag 500 + # - no vpn id 4:5 + # - vrf definition VRF7 + # + # after: + # - name: VRF2 + # - name: VRF6 + # - name: VRF7 + + # After state: + # ------------- + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vrf definition VRF7 + + # Using Deleted with empty config + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vnet tag 500 + # description VRF6 description + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 6:7 + # vpn id 4:5 + # route-target export 198.51.0.2:400 + # route-target import 198.51.0.5:200 + # vrf definition VRF7 + + - name: Delete the provided configuration when config is empty + cisco.ios.ios_vrf_global: + config: + state: deleted + + # Task Output: + # ------------ + # + # before: + # - name: VRF2 + # - name: VRF6 + # description: VRF6 description + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "6:7" + # route_target: + # export: "198.51.0.2:400" + # import_config: "198.51.0.5:200" + # vnet: + # tag: 500 + # vpn: + # id: "4:5" + # - name: VRF7 + + # commands: + # - vrf definition VRF2 + # - vrf definition VRF6 + # - no description VRF6 description + # - no ipv4 multicast multitopology + # - no ipv6 multicast multitopology + # - no rd 6:7 + # - no route-target export 198.51.0.2:400 + # - no route-target import 198.51.0.5:200 + # - no vnet tag 500 + # - no vpn id 4:5 + # - vrf definition VRF7 + # + # after: + # - name: VRF2 + # - name: VRF6 + # - name: VRF7 + + # After state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vrf definition VRF7 + + # Using purged - would delete all the VRF definitions + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vrf definition VRF6 + # vrf definition VRF7 + + - name: Purge all the configuration from the device + cisco.ios.ios_vrf_global: + state: purged + + # Task Output: + # ------------ + # + # before: + # - name: VRF2 + # - name: VRF6 + # - name: VRF7 + # commands: + # - no vrf definition VRF2 + # - no vrf definition VRF6 + # - no vrf definition VRF7 + # after: {} + + # After state: + # ------------- + # + # admin#show running-config | section ^vrf + + # Using Rendered + + - name: Render provided configuration with device configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.0:100" + import_config: "192.0.2.3:200" + vpn: + id: "2:45" + vnet: + tag: 200 + state: rendered + + # Task Output: + # ------------ + # + # rendered: + # - vrf definition VRF2 + # - description This is a test VRF for merged state + # - ipv4 multicast multitopology + # - ipv6 multicast multitopology + # - rd 2:3 + # - route-target export 192.0.2.0:100 + # - route-target import 192.0.2.3:200 + # - vnet tag 200 + # - vpn id 2:45 + + # Using Gathered + + # Before state: + # ------------- + # + # admin#show running-config | section ^vrf + # vrf definition VRF2 + # vnet tag 200 + # description This is a test VRF for merged state + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 2:3 + # vpn id 2:45 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.3:200 + + - name: Gather existing running configuration + cisco.ios.ios_vrf_global: + config: + state: gathered + + # Task Output: + # ------------ + # + # gathered: + # vrfs: + # - name: VRF2 + # description: This is a test VRF for merged state + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "2:3" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.3:200" + # vnet: + # tag: 200 + # vpn: + # id: "2:45" + + # Using parsed + + # File: parsed.cfg + # ---------------- + # + # vrf definition test + # vnet tag 34 + # description This is test VRF + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 192.0.2.0:300 + # vpn id 3:4 + # route-target export 192.0.2.0:100 + # route-target import 192.0.2.2:300 + # vrf definition test2 + # vnet tag 35 + # description This is test VRF + # ipv4 multicast multitopology + # ipv6 multicast multitopology + # rd 192.0.2.3:300 + + - name: Parse the provided configuration + cisco.ios.ios_vrf_global: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + + # Task Output: + # ------------ + # + # parsed: + # vrfs: + # - name: test + # description: This is test VRF + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "192.0.2.0:300" + # route_target: + # export: "192.0.2.0:100" + # import_config: "192.0.2.2:300" + # vnet: + # tag: 34 + # vpn: + # id: "3:4" + # - name: test2 + # description: This is test VRF + # ipv4: + # multicast: + # multitopology: true + # ipv6: + # multicast: + # multitopology: true + # rd: "192.0.2.3:300" + # vnet: + # tag: 35 + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyReturnedDescription
+
+ after + +
+ dictionary +
+
when changed +
The resulting configuration after module execution.
+
+
Sample:
+
This output will always be in the same format as the module argspec.
+
+
+ before + +
+ dictionary +
+
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 definition test', 'description This is a test VRF', 'rd: 2:3']
+
+
+ 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 definition management', 'description This is a test VRF', 'rd: 2:3', 'route-target export 190.0.2.3:400', 'route-target import 190.0.2.1:300']
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Ruchi Pakhle (@Ruchip16) diff --git a/meta/runtime.yml b/meta/runtime.yml index 6a478ac31..00d12034b 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -83,4 +83,6 @@ plugin_routing: redirect: cisco.ios.ios_vrf vxlan_vtep: redirect: cisco.ios.ios_vxlan_vtep + vrf_global: + redirect: cisco.ios.ios_vrf_global requires_ansible: ">=2.15.0" diff --git a/plugins/action/vrf_global.py b/plugins/action/vrf_global.py new file mode 120000 index 000000000..7747aa9dd --- /dev/null +++ b/plugins/action/vrf_global.py @@ -0,0 +1 @@ +ios.py \ No newline at end of file diff --git a/plugins/module_utils/network/ios/argspec/vrf_global/__init__.py b/plugins/module_utils/network/ios/argspec/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py new file mode 100644 index 000000000..87a84bbf7 --- /dev/null +++ b/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py @@ -0,0 +1,99 @@ +# -*- 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 ios_vrf_global module +""" + + +class Vrf_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the ios_vrf_global module""" + + argument_spec = { + "config": { + "type": "dict", + "options": { + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "description": {"type": "str"}, + "ipv4": { + "type": "dict", + "options": { + "multicast": { + "type": "dict", + "options": {"multitopology": {"type": "bool"}}, + }, + }, + }, + "ipv6": { + "type": "dict", + "options": { + "multicast": { + "type": "dict", + "options": {"multitopology": {"type": "bool"}}, + }, + }, + }, + "rd": {"type": "str"}, + "route_target": { + "type": "dict", + "options": { + "export": {"type": "str"}, + "import_config": {"type": "str"}, + "both": {"type": "str"}, + }, + }, + "vnet": { + "type": "dict", + "options": {"tag": {"type": "int"}}, + }, + "vpn": { + "type": "dict", + "options": {"id": {"type": "str"}}, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "parsed", + "gathered", + "deleted", + "merged", + "replaced", + "rendered", + "overridden", + "purged", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/ios/config/vrf_global/__init__.py b/plugins/module_utils/network/ios/config/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py new file mode 100644 index 000000000..476be76e0 --- /dev/null +++ b/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py @@ -0,0 +1,117 @@ +# -*- 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 ios_vrf_global 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.ios.plugins.module_utils.network.ios.facts.facts import Facts +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vrf_global import ( + Vrf_globalTemplate, +) + + +class Vrf_global(ResourceModule): + """ + The ios_vrf_global config class + """ + + def __init__(self, module): + super(Vrf_global, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vrf_global", + tmplt=Vrf_globalTemplate(), + ) + self.parsers = [ + "description", + "ipv4.multicast.multitopology", + "ipv6.multicast.multitopology", + "rd", + "route_target.export", + "route_target.import_config", + "route_target.both", + "vnet.tag", + "vpn.id", + ] + + 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. + """ + haved, wantd = dict(), dict() + + if self.want: + for entry in self.want.get("vrfs", []): + wantd.update({(entry["name"]): entry}) + + if self.have: + for entry in self.have.get("vrfs", []): + haved.update({(entry["name"]): entry}) + + # if state is merged, merge want onto have + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, limit the have to anything in want & set want to nothing + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + if self.state == "purged": + for k, have in iteritems(haved): + self.purge(have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Vrf_global network resource. + """ + if want != have: + self.addcmd(want or have, "name", False) + self.compare(self.parsers, want, have) + + def purge(self, have): + """Purge the VRF configuration""" + self.commands.append("no vrf definition {0}".format(have["name"])) diff --git a/plugins/module_utils/network/ios/facts/facts.py b/plugins/module_utils/network/ios/facts/facts.py index 583c86b51..7e7266b76 100644 --- a/plugins/module_utils/network/ios/facts/facts.py +++ b/plugins/module_utils/network/ios/facts/facts.py @@ -99,6 +99,9 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vlans.vlans import ( VlansFacts, ) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vrf_global.vrf_global import ( + Vrf_globalFacts, +) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vxlan_vtep.vxlan_vtep import ( Vxlan_vtepFacts, ) @@ -139,6 +142,7 @@ vxlan_vtep=Vxlan_vtepFacts, evpn_global=Evpn_globalFacts, evpn_evi=Evpn_eviFacts, + vrf_global=Vrf_globalFacts, ) diff --git a/plugins/module_utils/network/ios/facts/vrf_global/__init__.py b/plugins/module_utils/network/ios/facts/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py new file mode 100644 index 000000000..3253562e3 --- /dev/null +++ b/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The ios vrf_global 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.ios.plugins.module_utils.network.ios.argspec.vrf_global.vrf_global import ( + Vrf_globalArgs, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vrf_global import ( + Vrf_globalTemplate, +) + + +class Vrf_globalFacts(object): + """The ios vrf_global facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Vrf_globalArgs.argument_spec + + def get_config(self, connection): + """Get the configuration from the device""" + + return connection.get("show running-config | section ^vrf") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vrf_global network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + vrf_global_parser = Vrf_globalTemplate(lines=data.splitlines(), module=self._module) + objs = vrf_global_parser.parse() + + # Convert the dictionary to a list of dictionaries + objs["vrfs"] = list(objs["vrfs"].values()) if "vrfs" in objs else [] + + ansible_facts["ansible_network_resources"].pop("vrf_global", None) + params = utils.remove_empties( + vrf_global_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["vrf_global"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/ios/rm_templates/vrf_global.py b/plugins/module_utils/network/ios/rm_templates/vrf_global.py new file mode 100644 index 000000000..0563f461f --- /dev/null +++ b/plugins/module_utils/network/ios/rm_templates/vrf_global.py @@ -0,0 +1,225 @@ +# -*- 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_global parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class Vrf_globalTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Vrf_globalTemplate, self).__init__( + lines=lines, + tmplt=self, + module=module, + ) + + # fmt: off + PARSERS = [ + { + "name": "name", + "getval": re.compile( + r""" + ^vrf\sdefinition + (\s(?P\S+))? + $""", re.VERBOSE, + ), + "setval": "vrf definition {{ name }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + }, + }, + }, + "shared": True, + }, + { + "name": "description", + "getval": re.compile( + r""" + \s+description\s(?P.+$) + $""", re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'description': '{{ description }}', + }, + }, + }, + }, + { + "name": "ipv4.multicast.multitopology", + "getval": re.compile( + r""" + \s+ipv4\smulticast\s(?Pmultitopology) + $""", re.VERBOSE, + ), + "setval": "ipv4 multicast multitopology", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'ipv4': { + 'multicast': { + 'multitopology': "{{ true if multitopology is defined }}", + }, + }, + }, + }, + }, + }, + { + "name": "ipv6.multicast.multitopology", + "getval": re.compile( + r""" + \s+ipv6\smulticast\s(?Pmultitopology) + $""", re.VERBOSE, + ), + "setval": "ipv6 multicast multitopology", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'ipv6': { + 'multicast': { + 'multitopology': "{{ true if multitopology is defined }}", + }, + }, + }, + }, + }, + }, + { + "name": "rd", + "getval": re.compile( + r""" + \s+rd\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "rd {{ rd }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "rd": "{{ rd }}", + }, + }, + }, + }, + { + "name": "route_target.export", + "getval": re.compile( + r""" + \s+route-target\sexport\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "route-target export {{ route_target.export }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "export": "{{ route_target_export }}", + }, + }, + }, + }, + }, + { + "name": "route_target.import_config", + "getval": re.compile( + r""" + \s+route-target\simport\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "route-target import {{ route_target.import_config}}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "import_config": "{{ route_target_import_config }}", + }, + }, + }, + }, + }, + { + "name": "route_target.both", + "getval": re.compile( + r""" + \s+route-target\sboth\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "route-target both {{ route_target.both }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "both": "{{ route_target_both }}", + }, + }, + }, + }, + }, + { + "name": "vnet.tag", + "getval": re.compile( + r""" + \s+vnet\stag\s(?P\d+) + $""", re.VERBOSE, + ), + "setval": "vnet tag {{ vnet.tag }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "vnet": { + "tag": "{{ vnet_tag }}", + }, + }, + }, + }, + }, + { + "name": "vpn.id", + "getval": re.compile( + r""" + \s+vpn\sid\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "vpn id {{ vpn.id }}", + "result": { + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "vpn": { + "id": "{{ vpn_id }}", + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/plugins/modules/ios_vrf_global.py b/plugins/modules/ios_vrf_global.py new file mode 100644 index 000000000..5b737c6f1 --- /dev/null +++ b/plugins/modules/ios_vrf_global.py @@ -0,0 +1,892 @@ +#!/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 ios_vrf_global +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: ios_vrf_global +short_description: Resource module to configure global VRF definitions. +description: This module provides declarative management of VRF definitions on Cisco IOS. +version_added: 8.0.0 +author: Ruchi Pakhle (@Ruchip16) +notes: + - Tested against Cisco IOS-XE version 17.3 on CML. + - This module works with connection C(network_cli). + See U(https://docs.ansible.com/ansible/latest/network/user_guide/platform_ios.html) + - The module examples uses callback plugin (stdout_callback = yaml) to generate task + output in yaml format. +options: + config: + description: A dictionary containing device configurations for VRF, including a list of VRF definitions. + type: dict + suboptions: + vrfs: + description: List of VRF definitions. + type: list + elements: dict + suboptions: + name: + description: Name of the VRF. + type: str + required: true + description: + description: VRF specific description + type: str + ipv4: + description: VRF IPv4 configuration + type: dict + suboptions: + multicast: + description: IPv4 multicast configuration + type: dict + suboptions: + multitopology: + description: Enable multicast-specific topology + type: bool + ipv6: + description: VRF IPv6 configuration + type: dict + suboptions: + multicast: + description: IPv6 multicast configuration + type: dict + suboptions: + multitopology: + description: Enable multicast-specific topology + type: bool + rd: + description: Specify route distinguisher (RD). + type: str + route_target: + description: Specify target VPN extended configurations. + type: dict + suboptions: + export: + description: Export target-VPN configuration. + type: str + import_config: + description: Import target-VPN configuration. + type: str + both: + description: Both export and import target-VPN configuration. + type: str + vnet: + description: Virtual networking configuration. + type: dict + suboptions: + tag: + description: Identifier used to tag packets associated with this VNET. + type: int + vpn: + description: Configure vpn-id for the VRF as specified in RFC 2685. + type: dict + suboptions: + id: + description: Configure vpn-id in RFC 2685 format. + type: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the IOS 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, merged, replaced, rendered, overridden, purged] + 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. + - The state I(deleted) only removes the VRF attributes that this module + manages and does not negate the VRF completely. Thereby, preserving + address-family related configurations under VRF context. + - The state I(purged) removes all the VRF definitions from the + target device. Use caution with this state. + - Refer to examples for more details. + type: str +""" + +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf + +- name: Merge provided configuration with device configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.0:100" + import_config: "192.0.2.3:200" + vpn: + id: "2:45" + vnet: + tag: 200 + state: merged + +# Task output +# ------------- +# +# before: {} +# +# commands: +# - vrf definition VRF2 +# - description This is a test VRF for merged state +# - ipv4 multicast multitopology +# - ipv6 multicast multitopology +# - rd 2:3 +# - route-target export 192.0.2.0:100 +# - route-target import 192.0.2.3:200 +# - vnet tag 200 +# - vpn id 2:45 +# +# after: +# - name: VRF2 +# description: This is a test VRF for merged state +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "2:3" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.3:200" +# vnet: +# tag: 200 +# vpn: +# id: "2:45" + +# After state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vnet tag 200 +# description This is a test VRF for merged state +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 2:3 +# vpn id 2:45 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.3:200 + +# Using replaced + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vnet tag 200 +# description This is a test VRF for merged state +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 2:3 +# vpn id 2:45 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.3:200 + +- name: Replace the provided configuration with the existing running configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF7 + description: VRF7 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "7:8" + route_target: + export: "198.51.100.112:500" + import_config: "192.0.2.4:400" + vpn: + id: "5:45" + vnet: + tag: 300 + state: replaced + +# Task Output: +# ------------ +# +# before: +# - name: VRF2 +# description: This is a test VRF for merged state +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "2:3" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.3:200" +# vnet: +# tag: 200 +# vpn: +# id: "2:45" +# +# commands: +# - vrf definition VRF7 +# - description VRF7 description +# - ipv4 multicast multitopology +# - ipv6 multicast multitopology +# - rd 7:8 +# - route-target export 198.51.100.112:500 +# - route-target import 192.0.2.4:400 +# - vnet tag 300 +# - vpn id 5:45 +# +# after: +# - name: VRF2 +# description: This is a test VRF for merged state +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "2:3" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.3:200" +# vnet: +# tag: 200 +# vpn: +# id: "2:45 +# - name: VRF7 +# description: VRF7 description +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "7:8" +# route_target: +# export: "198.51.100.112:500" +# import_config: "192.0.2.4:400" +# vnet: +# tag: 300 +# vpn: +# id: "5:45" +# +# After state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vnet tag 200 +# description This is a test VRF for merged state +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 2:3 +# vpn id 2:45 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.3:200 +# vrf definition VRF7 +# vnet tag 300 +# description VRF7 description +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 7:8 +# route-target export 198.51.100.112:500 +# route-target import 192.0.2.4:400 +# vpn id 5:45 + +# Using Overridden + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vnet tag 200 +# description This is a test VRF for merged state +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 2:3 +# vpn id 2:45 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.3:200 +# vrf definition VRF7 +# vnet tag 300 +# description VRF7 description +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 7:8 +# route-target export 198.51.100.112:500 +# route-target import 192.0.2.4:400 +# vpn id 5:45 + +- name: Override the provided configuration with the existing running configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF6 + description: VRF6 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "6:7" + route_target: + export: "198.51.0.2:400" + import_config: "198.51.0.5:200" + vpn: + id: "4:5" + vnet: + tag: 500 + state: overridden + +# Task Output: +# ------------ +# +# before: +# - name: VRF2 +# description: This is a test VRF for merged state +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "2:3" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.3:200" +# vnet: +# tag: 200 +# vpn: +# id: "2:45 +# - name: VRF7 +# description: VRF7 description +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "7:8" +# route_target: +# export: "198.51.100.112:500" +# import_config: "192.0.2.4:400" +# vnet: +# tag: 300 +# vpn: +# id: "5:45" +# +# commands: +# - vrf definition VRF2 +# - no description This is a test VRF for merged state +# - no ipv4 multicast multitopology +# - no ipv6 multicast multitopology +# - no rd 2:3 +# - no route-target export 192.0.2.0:100 +# - no route-target import 192.0.2.3:200 +# - no vnet tag 200 +# - no vpn id 2:45 +# - vrf definition VRF7 +# - no description VRF7 description +# - no ipv4 multicast multitopology +# - no ipv6 multicast multitopology +# - no rd 7:8 +# - no route-target export 198.51.100.112:500 +# - no route-target import 192.0.2.4:400 +# - no vnet tag 300 +# - no vpn id 5:45 +# - vrf definition VRF6 +# - description VRF6 description +# - ipv4 multicast multitopology +# - ipv6 multicast multitopology +# - rd 6:7 +# - route-target export 198.51.0.2:400 +# - route-target import 198.51.0.5:200 +# - vnet tag 500 +# - vpn id 4:5 +# +# after: +# - name: VRF2 +# - name: VRF6 +# description: VRF6 description +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "6:7" +# route_target: +# export: "198.51.0.2:400" +# import_config: "198.51.0.5:200" +# vnet: +# tag: 500 +# vpn: +# id: "4:5 +# - name: VRF7 + +# After state: +# ------------ +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vnet tag 500 +# description VRF6 description +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 6:7 +# vpn id 4:5 +# route-target export 198.51.0.2:400 +# route-target import 198.51.0.5:200 +# vrf definition VRF7 + +# Using Deleted + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vnet tag 500 +# description VRF6 description +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 6:7 +# vpn id 4:5 +# route-target export 198.51.0.2:400 +# route-target import 198.51.0.5:200 +# vrf definition VRF7 + +- name: Delete the provided configuration when config is given + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + - name: VRF6 + - name: VRF7 + state: deleted + +# Task Output: +# ------------ +# +# before: +# - name: VRF2 +# - name: VRF6 +# description: VRF6 description +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "6:7" +# route_target: +# export: "198.51.0.2:400" +# import_config: "198.51.0.5:200" +# vnet: +# tag: 500 +# vpn: +# id: "4:5" +# - name: VRF7 +# +# commands: +# - vrf definition VRF2 +# - vrf definition VRF6 +# - no description VRF6 description +# - no ipv4 multicast multitopology +# - no ipv6 multicast multitopology +# - no rd 6:7 +# - no route-target export 198.51.0.2:400 +# - no route-target import 198.51.0.5:200 +# - no vnet tag 500 +# - no vpn id 4:5 +# - vrf definition VRF7 +# +# after: +# - name: VRF2 +# - name: VRF6 +# - name: VRF7 + +# After state: +# ------------- +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vrf definition VRF7 + +# Using Deleted with empty config + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vnet tag 500 +# description VRF6 description +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 6:7 +# vpn id 4:5 +# route-target export 198.51.0.2:400 +# route-target import 198.51.0.5:200 +# vrf definition VRF7 + +- name: Delete the provided configuration when config is empty + cisco.ios.ios_vrf_global: + config: + state: deleted + +# Task Output: +# ------------ +# +# before: +# - name: VRF2 +# - name: VRF6 +# description: VRF6 description +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "6:7" +# route_target: +# export: "198.51.0.2:400" +# import_config: "198.51.0.5:200" +# vnet: +# tag: 500 +# vpn: +# id: "4:5" +# - name: VRF7 + +# commands: +# - vrf definition VRF2 +# - vrf definition VRF6 +# - no description VRF6 description +# - no ipv4 multicast multitopology +# - no ipv6 multicast multitopology +# - no rd 6:7 +# - no route-target export 198.51.0.2:400 +# - no route-target import 198.51.0.5:200 +# - no vnet tag 500 +# - no vpn id 4:5 +# - vrf definition VRF7 +# +# after: +# - name: VRF2 +# - name: VRF6 +# - name: VRF7 + +# After state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vrf definition VRF7 + +# Using purged - would delete all the VRF definitions + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vrf definition VRF6 +# vrf definition VRF7 + +- name: Purge all the configuration from the device + cisco.ios.ios_vrf_global: + state: purged + +# Task Output: +# ------------ +# +# before: +# - name: VRF2 +# - name: VRF6 +# - name: VRF7 +# commands: +# - no vrf definition VRF2 +# - no vrf definition VRF6 +# - no vrf definition VRF7 +# after: {} + +# After state: +# ------------- +# +# admin#show running-config | section ^vrf + +# Using Rendered + +- name: Render provided configuration with device configuration + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.0:100" + import_config: "192.0.2.3:200" + vpn: + id: "2:45" + vnet: + tag: 200 + state: rendered + +# Task Output: +# ------------ +# +# rendered: +# - vrf definition VRF2 +# - description This is a test VRF for merged state +# - ipv4 multicast multitopology +# - ipv6 multicast multitopology +# - rd 2:3 +# - route-target export 192.0.2.0:100 +# - route-target import 192.0.2.3:200 +# - vnet tag 200 +# - vpn id 2:45 + +# Using Gathered + +# Before state: +# ------------- +# +# admin#show running-config | section ^vrf +# vrf definition VRF2 +# vnet tag 200 +# description This is a test VRF for merged state +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 2:3 +# vpn id 2:45 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.3:200 + +- name: Gather existing running configuration + cisco.ios.ios_vrf_global: + config: + state: gathered + +# Task Output: +# ------------ +# +# gathered: +# vrfs: +# - name: VRF2 +# description: This is a test VRF for merged state +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "2:3" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.3:200" +# vnet: +# tag: 200 +# vpn: +# id: "2:45" + +# Using parsed + +# File: parsed.cfg +# ---------------- +# +# vrf definition test +# vnet tag 34 +# description This is test VRF +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 192.0.2.0:300 +# vpn id 3:4 +# route-target export 192.0.2.0:100 +# route-target import 192.0.2.2:300 +# vrf definition test2 +# vnet tag 35 +# description This is test VRF +# ipv4 multicast multitopology +# ipv6 multicast multitopology +# rd 192.0.2.3:300 + +- name: Parse the provided configuration + cisco.ios.ios_vrf_global: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task Output: +# ------------ +# +# parsed: +# vrfs: +# - name: test +# description: This is test VRF +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "192.0.2.0:300" +# route_target: +# export: "192.0.2.0:100" +# import_config: "192.0.2.2:300" +# vnet: +# tag: 34 +# vpn: +# id: "3:4" +# - name: test2 +# description: This is test VRF +# ipv4: +# multicast: +# multitopology: true +# ipv6: +# multicast: +# multitopology: true +# rd: "192.0.2.3:300" +# vnet: +# tag: 35 +""" + +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: dict + 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: dict + 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 definition test" + - "description This is a test VRF" + - "rd: 2:3" +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 definition management" + - "description This is a test VRF" + - "rd: 2:3" + - "route-target export 190.0.2.3:400" + - "route-target import 190.0.2.1:300" +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.ios.plugins.module_utils.network.ios.argspec.vrf_global.vrf_global import ( + Vrf_globalArgs, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.config.vrf_global.vrf_global import ( + Vrf_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vrf_globalArgs.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_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/test/__init__.py b/plugins/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/targets/ios_vrf_global/defaults/main.yaml b/tests/integration/targets/ios_vrf_global/defaults/main.yaml new file mode 100644 index 000000000..164afead2 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/ios_vrf_global/meta/main.yaml b/tests/integration/targets/ios_vrf_global/meta/main.yaml new file mode 100644 index 000000000..23d65c7ef --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/meta/main.yaml @@ -0,0 +1,2 @@ +--- +dependencies: [] diff --git a/tests/integration/targets/ios_vrf_global/tasks/cli.yaml b/tests/integration/targets/ios_vrf_global/tasks/cli.yaml new file mode 100644 index 000000000..6f505600c --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tasks/cli.yaml @@ -0,0 +1,21 @@ +--- +- name: Collect all CLI test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/cli" + 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/ios_vrf_global/tasks/main.yaml b/tests/integration/targets/ios_vrf_global/tasks/main.yaml new file mode 100644 index 000000000..adc4c6075 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tasks/main.yaml @@ -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/ios_vrf_global/tests/cli/_parsed.cfg b/tests/integration/targets/ios_vrf_global/tests/cli/_parsed.cfg new file mode 100644 index 000000000..5e237fe62 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/_parsed.cfg @@ -0,0 +1,11 @@ +vrf definition test + vnet tag 34 + description This is test VRF + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + vpn id 3:4 + route-target export 192.0.2.0:100 + route-target import 192.0.2.3:300 +! +end diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/_populate_config.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/_populate_config.yaml new file mode 100644 index 000000000..0d0a17388 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/_populate_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Merge provided configuration with device configuration + register: result + cisco.ios.ios_config: + lines: + - vrf definition VRF2 + - description This is a test VRF for merged state + - ipv4 multicast multitopology + - ipv6 multicast multitopology + - rd 2:3 + - route-target export 192.0.2.1:400 + - route-target import 192.0.2.6:400 + - vnet tag 200 + - vpn id 2:45 + save_when: always diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/_remove_config.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/_remove_config.yaml new file mode 100644 index 000000000..f662e3363 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/_remove_config.yaml @@ -0,0 +1,10 @@ +--- +- name: Remove VRF global configurations + cisco.ios.ios_config: + lines: + - no vrf definition VRF2 + - no vrf definition VRF7 + - no vrf definition VRF6 + - no vrf definition test + save_when: always + register: result diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/deleted.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/deleted.yaml new file mode 100644 index 000000000..68ac217b0 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/deleted.yaml @@ -0,0 +1,45 @@ +--- +- ansible.builtin.debug: + msg: Start Deleted integration state for ios_vrf_global ansible_connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Delete given vrf configuration + register: result + cisco.ios.ios_vrf_global: &id001 + config: + vrfs: + - name: VRF2 + 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: + - "{{ merged['after']['vrfs'] | symmetric_difference(result['before']['vrfs']) | 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.ios.ios_vrf_global: *id001 + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result.changed == true + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/empty_config.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/empty_config.yaml new file mode 100644 index 000000000..7c8474844 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/empty_config.yaml @@ -0,0 +1,68 @@ +--- +- ansible.builtin.debug: + msg: START ios_vrf_global 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.ios.ios_vrf_global: + 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.ios.ios_vrf_global: + 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.ios.ios_vrf_global: + 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.ios.ios_vrf_global: + 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.ios.ios_vrf_global: + 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.ios.ios_vrf_global: + config: + state: purged + +- ansible.builtin.debug: + msg: END ios_vrf_global empty_config integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/gathered.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/gathered.yaml new file mode 100644 index 000000000..cf2f2972b --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/gathered.yaml @@ -0,0 +1,22 @@ +--- +- ansible.builtin.debug: + msg: START ios_vrf_global 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.ios.ios_vrf_global: + config: + state: gathered + + - name: Assert + ansible.builtin.assert: + that: + - result.changed == false + - gathered['after'] == result['gathered'] + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/merged.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/merged.yaml new file mode 100644 index 000000000..f705627f6 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/merged.yaml @@ -0,0 +1,54 @@ +--- +- ansible.builtin.debug: + msg: START Merged ios_vrf_global 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.ios.ios_vrf_global: &id001 + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + state: merged + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - merged['before'] == {} + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - merged['after'] == result['after'] + + - name: Merge provided configuration with device configuration (idempotent) + register: result + cisco.ios.ios_vrf_global: *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/ios_vrf_global/tests/cli/overridden.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/overridden.yaml new file mode 100644 index 000000000..664971e3c --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/overridden.yaml @@ -0,0 +1,63 @@ +--- +- ansible.builtin.debug: + msg: START ios_vrf_global 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.ios.ios_vrf_global: &overridden + config: + vrfs: + - name: VRF2 + - name: VRF6 + description: VRF6 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "6:7" + route_target: + export: "192.0.2.2:300" + import_config: "192.0.2.3:400" + vpn: + id: "4:5" + vnet: + tag: 500 + 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.ios.ios_vrf_global: *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/ios_vrf_global/tests/cli/parsed.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/parsed.yaml new file mode 100644 index 000000000..0d1008d1a --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/parsed.yaml @@ -0,0 +1,14 @@ +--- +- ansible.builtin.debug: + msg: START ios_vrf_global parsed integration tests on connection={{ ansible_connection }} + +- name: Parse the commands for provided configuration + register: result + cisco.ios.ios_vrf_global: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- ansible.builtin.assert: + that: + - result.changed == false + - parsed['after'] == result['parsed'] diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/purged.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/purged.yaml new file mode 100644 index 000000000..aa016c607 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/purged.yaml @@ -0,0 +1,29 @@ +--- +- ansible.builtin.debug: + msg: Start Deleted integration state for ios_vrf_global ansible_connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Purge provided VRF global + register: result + cisco.ios.ios_vrf_global: &id001 + state: purged + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "'no vrf definition VRF2' in result.commands" + - result.commands|length == 1 + + - name: Purge provided VRF global (idempotent) + register: result + cisco.ios.ios_vrf_global: *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/ios_vrf_global/tests/cli/rendered.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/rendered.yaml new file mode 100644 index 000000000..3eebab3b5 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/rendered.yaml @@ -0,0 +1,34 @@ +--- +- ansible.builtin.debug: + msg: Start ios_vrf_global rendered integration tests ansible_connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Render provided configuration with device configuration + register: result + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + state: rendered + + - ansible.builtin.assert: + that: + - result.changed == false + - result.rendered|symmetric_difference(merged.commands) == [] diff --git a/tests/integration/targets/ios_vrf_global/tests/cli/replaced.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/replaced.yaml new file mode 100644 index 000000000..69842bd71 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/replaced.yaml @@ -0,0 +1,55 @@ +--- +- ansible.builtin.debug: + msg: START replaced ios_vrf_global state for integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml +- ansible.builtin.include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + cisco.ios.ios_vrf_global: &id001 + config: + vrfs: + - name: VRF7 + description: VRF7 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "7:8" + route_target: + export: "198.51.100.2:500" + import_config: "198.51.100.5:400" + vpn: + id: "5:45" + vnet: + tag: 300 + 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.ios.ios_vrf_global: *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/ios_vrf_global/tests/cli/rtt.yaml b/tests/integration/targets/ios_vrf_global/tests/cli/rtt.yaml new file mode 100644 index 000000000..2273b8bf3 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/tests/cli/rtt.yaml @@ -0,0 +1,77 @@ +--- +- ansible.builtin.debug: + msg: START ios_vrf_global round trip integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Merge provided configuration with device configuration + register: result + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + state: merged + + - name: Ios_vrf_global RTT - gather vrf_global facts + cisco.ios.ios_facts: + gather_network_resources: + - vrf_global + + - name: Apply the provided configuration (config to be reverted) + register: result + cisco.ios.ios_vrf_global: + config: + vrfs: + - name: VRF6 + description: VRF6 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "6:7" + route_target: + export: "192.0.2.2:300" + import_config: "192.0.2.3:400" + vpn: + id: "4:5" + vnet: + tag: 500 + state: overridden + + - ansible.builtin.assert: + that: + - result.commands|length == 18 + - result.changed == true + - result.commands|symmetric_difference(rtt.commands) == [] + + - name: Revert back to base configuration using facts round trip + register: revert + cisco.ios.ios_vrf_global: + config: "{{ ansible_facts['network_resources']['vrf_global'] }}" + state: replaced + + - ansible.builtin.assert: + that: + - revert.changed == true + - revert['commands'] == merged['commands'] + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/ios_vrf_global/vars/main.yaml b/tests/integration/targets/ios_vrf_global/vars/main.yaml new file mode 100644 index 000000000..7573f6484 --- /dev/null +++ b/tests/integration/targets/ios_vrf_global/vars/main.yaml @@ -0,0 +1,233 @@ +--- +merged: + before: {} + + commands: + - vrf definition VRF2 + - description This is a test VRF for merged state + - ipv4 multicast multitopology + - ipv6 multicast multitopology + - rd 2:3 + - route-target export 192.0.2.1:400 + - route-target import 192.0.2.6:400 + - vnet tag 200 + - vpn id 2:45 + + after: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + +replaced: + before: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + commands: + - vrf definition VRF7 + - description VRF7 description + - ipv4 multicast multitopology + - ipv6 multicast multitopology + - rd 7:8 + - route-target export 198.51.100.2:500 + - route-target import 198.51.100.5:400 + - vnet tag 300 + - vpn id 5:45 + after: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + - name: VRF7 + description: VRF7 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "7:8" + route_target: + export: "198.51.100.2:500" + import_config: "198.51.100.5:400" + vpn: + id: "5:45" + vnet: + tag: 300 + +overridden: + before: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + + commands: + - vrf definition VRF2 + - no description This is a test VRF for merged state + - no ipv4 multicast multitopology + - no ipv6 multicast multitopology + - no rd 2:3 + - no route-target export 192.0.2.1:400 + - no route-target import 192.0.2.6:400 + - no vnet tag 200 + - no vpn id 2:45 + - vrf definition VRF6 + - description VRF6 description + - ipv4 multicast multitopology + - ipv6 multicast multitopology + - rd 6:7 + - route-target export 192.0.2.2:300 + - route-target import 192.0.2.3:400 + - vnet tag 500 + - vpn id 4:5 + after: + vrfs: + - name: VRF2 + - name: VRF6 + description: VRF6 description + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "6:7" + route_target: + export: "192.0.2.2:300" + import_config: "192.0.2.3:400" + vpn: + id: "4:5" + vnet: + tag: 500 + +gathered: + after: + vrfs: + - name: VRF2 + description: This is a test VRF for merged state + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.1:400" + import_config: "192.0.2.6:400" + vpn: + id: "2:45" + vnet: + tag: 200 + +parsed: + after: + vrfs: + - name: test + description: This is test VRF + ipv4: + multicast: + multitopology: true + ipv6: + multicast: + multitopology: true + rd: "2:3" + route_target: + export: "192.0.2.0:100" + import_config: "192.0.2.3:300" + vpn: + id: "3:4" + vnet: + tag: 34 + +deleted: + commands: + - vrf definition VRF2 + - no description This is a test VRF for merged state + - no ipv4 multicast multitopology + - no ipv6 multicast multitopology + - no rd 2:3 + - no route-target export 192.0.2.1:400 + - no route-target import 192.0.2.6:400 + - no vnet tag 200 + - no vpn id 2:45 + + after: + vrfs: + - name: VRF2 + +rtt: + commands: + - vrf definition VRF2 + - no description This is a test VRF for merged state + - no ipv4 multicast multitopology + - no ipv6 multicast multitopology + - no rd 2:3 + - no route-target export 192.0.2.1:400 + - no route-target import 192.0.2.6:400 + - no vnet tag 200 + - no vpn id 2:45 + - vrf definition VRF6 + - description VRF6 description + - ipv4 multicast multitopology + - ipv6 multicast multitopology + - rd 6:7 + - route-target export 192.0.2.2:300 + - route-target import 192.0.2.3:400 + - vnet tag 500 + - vpn id 4:5 diff --git a/tests/unit/modules/network/ios/test_ios_vrf_global.py b/tests/unit/modules/network/ios/test_ios_vrf_global.py new file mode 100644 index 000000000..4b04d6b85 --- /dev/null +++ b/tests/unit/modules/network/ios/test_ios_vrf_global.py @@ -0,0 +1,485 @@ +# (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.ios.plugins.modules import ios_vrf_global +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosVrfGlobalModule(TestIosModule): + """Test the ios_vrf_global module.""" + + module = ios_vrf_global + + def setUp(self): + """Set up for ios_vrf_global module tests.""" + super(TestIosVrfGlobalModule, 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.ios.plugins.module_utils.network.ios.facts.vrf_global.vrf_global." + "Vrf_globalFacts.get_config", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosVrfGlobalModule, self).tearDown() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def test_ios_vrf_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition test + description This is test VRF + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 192.0.2.0:100 + route-target import 192.0.2.3:300 + vnet tag 34 + vpn id 3:4 + """, + ) + + set_module_args( + dict( + config=dict( + vrfs=[ + dict( + name="VRF2", + description="This is a test VRF for merged state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict( + export="192.0.2.1:400", + import_config="192.0.2.6:400", + ), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + ), + state="merged", + ), + ) + commands = [ + "vrf definition VRF2", + "description This is a test VRF for merged state", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 2:3", + "route-target export 192.0.2.1:400", + "route-target import 192.0.2.6:400", + "vnet tag 200", + "vpn id 2:45", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + description This is a test VRF for merged state + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 192.0.2.1:400 + route-target import 192.0.2.6:400 + vnet tag 200 + vpn id 2:45 + """, + ) + set_module_args( + dict( + config=dict( + vrfs=[ + dict( + name="VRF2", + description="This is a test VRF for merged state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict( + export="192.0.2.1:400", + import_config="192.0.2.6:400", + ), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_vrf_global_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + description This is a test VRF for merged state + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 192.0.2.1:400 + route-target import 192.0.2.6:400 + vnet tag 200 + vpn id 2:45 + """, + ) + set_module_args( + dict( + config=dict( + vrfs=[ + dict( + name="VRF2", + ), + dict( + name="VRF6", + description="VRF6 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="6:7", + route_target=dict( + export="192.0.2.2:300", + import_config="192.0.2.3:400", + ), + vnet=dict(tag=500), + vpn=dict(id="4:5"), + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "vrf definition VRF2", + "no description This is a test VRF for merged state", + "no ipv4 multicast multitopology", + "no ipv6 multicast multitopology", + "no rd 2:3", + "no route-target export 192.0.2.1:400", + "no route-target import 192.0.2.6:400", + "no vnet tag 200", + "no vpn id 2:45", + "vrf definition VRF6", + "description VRF6 description", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 6:7", + "route-target export 192.0.2.2:300", + "route-target import 192.0.2.3:400", + "vnet tag 500", + "vpn id 4:5", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + vrf definition VRF6 + description VRF6 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 6:7 + route-target export 192.0.2.2:300 + route-target import 192.0.2.3:400 + vnet tag 500 + vpn id 4:5 + """, + ) + set_module_args( + dict( + config=dict( + vrfs=[ + dict( + name="VRF2", + ), + dict( + name="VRF6", + description="VRF6 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="6:7", + route_target=dict( + export="192.0.2.2:300", + import_config="192.0.2.3:400", + ), + vnet=dict(tag=500), + vpn=dict(id="4:5"), + ), + dict( + name="VRF7", + description="VRF7 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="7:8", + route_target=dict( + export="198.51.100.2:500", + import_config="198.51.100.5:400", + ), + vnet=dict(tag=300), + vpn=dict(id="2:45"), + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "vrf definition VRF7", + "description VRF7 description", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 7:8", + "route-target export 198.51.100.2:500", + "route-target import 198.51.100.5:400", + "vnet tag 300", + "vpn id 2:45", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + vrf definition VRF6 + vnet tag 500 + description VRF6 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 6:7 + vpn id 4:5 + route-target export 192.0.2.2:300 + route-target import 192.0.2.3:400 + vrf definition VRF7 + vnet tag 300 + description VRF7 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 7:8 + vpn id 2:45 + route-target export 198.51.100.2:500 + route-target import 198.51.100.5:400 + """, + ) + set_module_args( + dict( + config=dict( + vrfs=[ + dict( + name="VRF2", + ), + dict( + name="VRF6", + description="VRF6 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="6:7", + route_target=dict( + export="192.0.2.2:300", + import_config="192.0.2.3:400", + ), + vnet=dict(tag=500), + vpn=dict(id="4:5"), + ), + dict( + name="VRF7", + description="VRF7 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="7:8", + route_target=dict( + export="198.51.100.2:500", + import_config="198.51.100.5:400", + ), + vnet=dict(tag=300), + vpn=dict(id="2:45"), + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_vrf_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + vrf definition VRF6 + vnet tag 500 + description VRF6 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 6:7 + vpn id 4:5 + route-target export 192.0.2.2:300 + route-target import 192.0.2.3:400 + vrf definition VRF7 + vnet tag 300 + description VRF7 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 7:8 + vpn id 2:45 + route-target export 198.51.100.2:500 + route-target import 198.51.100.5:400 + """, + ) + set_module_args( + dict( + config=dict( + vrfs=[ + { + "name": "VRF6", + }, + { + "name": "VRF7", + }, + ], + ), + state="deleted", + ), + ) + commands = [ + "vrf definition VRF6", + "no description VRF6 description", + "no ipv4 multicast multitopology", + "no ipv6 multicast multitopology", + "no rd 6:7", + "no route-target export 192.0.2.2:300", + "no route-target import 192.0.2.3:400", + "no vnet tag 500", + "no vpn id 4:5", + "vrf definition VRF7", + "no description VRF7 description", + "no ipv4 multicast multitopology", + "no ipv6 multicast multitopology", + "no rd 7:8", + "no route-target export 198.51.100.2:500", + "no route-target import 198.51.100.5:400", + "no vnet tag 300", + "no vpn id 2:45", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_deleted_empty(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args(dict(config=dict(), state="deleted")) + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_ios_vrf_global_purged(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF7 + description VRF7 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 7:8 + route-target export 198.51.100.2:500 + route-target import 198.51.100.5:400 + vnet tag 300 + vpn id 2:45 + """, + ) + set_module_args(dict(state="purged")) + commands = ["no vrf definition VRF7"] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrf_global_rendered(self): + set_module_args( + dict( + config={ + "vrfs": [ + dict( + name="VRF2", + description="This is a test VRF for rendered state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict( + export="192.0.2.1:400", + import_config="192.0.2.6:400", + ), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + }, + state="rendered", + ), + ) + commands = [ + "vrf definition VRF2", + "description This is a test VRF for rendered state", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 2:3", + "route-target export 192.0.2.1:400", + "route-target import 192.0.2.6:400", + "vnet tag 200", + "vpn id 2:45", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_vrf_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + vrf definition test + description This is test VRF + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 192.0.2.0:100 + route-target import 192.0.2.3:300 + vnet tag 34 + vpn id 3:4 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = { + "vrfs": [ + { + "name": "test", + "description": "This is test VRF", + "ipv4": {"multicast": {"multitopology": True}}, + "ipv6": {"multicast": {"multitopology": True}}, + "rd": "2:3", + "route_target": {"export": "192.0.2.0:100", "import_config": "192.0.2.3:300"}, + "vnet": {"tag": 34}, + "vpn": {"id": "3:4"}, + }, + ], + } + self.assertEqual(parsed_list, result["parsed"])