diff --git a/README.md b/README.md index 6650cb67..7e06a029 100644 --- a/README.md +++ b/README.md @@ -1,264 +1,265 @@ # VyOS Collection [![codecov](https://codecov.io/gh/vyos/vyos.vyos/graph/badge.svg?token=J217GFD69W)](https://codecov.io/gh/vyos/vyos.vyos) [![CI](https://github.com/vyos/vyos.vyos/actions/workflows/tests.yml/badge.svg?branch=main&event=schedule)](https://github.com/vyos/vyos.vyos/actions/workflows/tests.yml) The Ansible VyOS collection includes a variety of Ansible content to help automate the management of VyOS network appliances. This collection has been tested against VyOS 1.3.8, 1.4.1 and the current rolling release for 1.5. Where possible, compatibility with older versions of VyOS are maintained but not guaranteed. ## Communication * Join the VyOS forum: * [FAQ](https://forum.vyos.io/faq): find answers to frequently asked questions. * [Guides and How To](https://forum.vyos.io/c/howto-guies/27): find guides and how-to articles. * [News & Announcements](https://forum.vyos.io/c/announcements/6): track project-wide announcements . ## Ansible version compatibility This collection has been tested against following Ansible versions: **>=2.15.0**. For collections that support Ansible 2.9, please ensure you update your `network_os` to use the fully qualified collection name (for example, `cisco.ios.ios`). Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions. PEP440 is the schema used to describe the versions of Ansible. ### Supported connections The VyOS collection supports ``network_cli`` connections. ## Included content ### Cliconf plugins Name | Description --- | --- [vyos.vyos.vyos](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_cliconf.rst)|Use vyos cliconf to run command on VyOS platform ### Modules Name | Description --- | --- [vyos.vyos.vyos_banner](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_banner_module.rst)|Manage multiline banners on VyOS devices [vyos.vyos.vyos_bgp_address_family](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_address_family_module.rst)|BGP Address Family resource module [vyos.vyos.vyos_bgp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_global_module.rst)|BGP global resource module [vyos.vyos.vyos_command](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_command_module.rst)|Run one or more commands on VyOS devices [vyos.vyos.vyos_config](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_config_module.rst)|Manage VyOS configuration on remote device [vyos.vyos.vyos_facts](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_facts_module.rst)|Get facts about vyos devices. [vyos.vyos.vyos_firewall_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_global_module.rst)|Firewall global resource module [vyos.vyos.vyos_firewall_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_interfaces_module.rst)|Firewall interfaces resource module [vyos.vyos.vyos_firewall_rules](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_rules_module.rst)|Firewall rules resource module [vyos.vyos.vyos_hostname](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_hostname_module.rst)|Manages hostname resource module [vyos.vyos.vyos_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interfaces_module.rst)|Manages interface attributes of VyOS network devices. [vyos.vyos.vyos_l3_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_l3_interfaces_module.rst)|Layer 3 interfaces resource module. [vyos.vyos.vyos_lag_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lag_interfaces_module.rst)|LAG interfaces resource module [vyos.vyos.vyos_lldp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_global_module.rst)|LLDP global resource module [vyos.vyos.vyos_lldp_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_interfaces_module.rst)|LLDP interfaces resource module [vyos.vyos.vyos_logging_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_logging_global_module.rst)|Logging resource module [vyos.vyos.vyos_ntp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ntp_global_module.rst)|NTP global resource module [vyos.vyos.vyos_ospf_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospf_interfaces_module.rst)|OSPF Interfaces Resource Module. [vyos.vyos.vyos_ospfv2](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv2_module.rst)|OSPFv2 resource module [vyos.vyos.vyos_ospfv3](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv3_module.rst)|OSPFv3 resource module [vyos.vyos.vyos_ping](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ping_module.rst)|Tests reachability using ping from VyOS network devices [vyos.vyos.vyos_prefix_lists](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_prefix_lists_module.rst)|Prefix-Lists resource module for VyOS [vyos.vyos.vyos_route_maps](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_route_maps_module.rst)|Route Map resource module [vyos.vyos.vyos_snmp_server](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_snmp_server_module.rst)|Manages snmp_server resource module [vyos.vyos.vyos_static_routes](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_static_routes_module.rst)|Static routes resource module [vyos.vyos.vyos_system](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_system_module.rst)|Run `set system` commands on VyOS devices [vyos.vyos.vyos_user](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_user_module.rst)|Manage the collection of local users on VyOS device [vyos.vyos.vyos_vlan](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vlan_module.rst)|Manage VLANs on VyOS network devices +[vyos.vyos.vyos_vrf](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vrf_module.rst)|VRF resource module Click the ``Content`` button to see the list of content included in this collection. ## Installing this collection You can install the VyOS collection with the Ansible Galaxy CLI: ansible-galaxy collection install vyos.vyos You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: ```yaml --- collections: - name: vyos.vyos ``` ## Using this collection This collection includes [network resource modules](https://docs.ansible.com/ansible/latest/network/user_guide/network_resource_modules.html). ### Using modules from the VyOS collection in your playbooks You can call modules by their Fully Qualified Collection Namespace (FQCN), such as `vyos.vyos.vyos_static_routes`. The following example task replaces configuration changes in the existing configuration on a VyOS network device, using the FQCN: ```yaml --- - name: Replace device configurations of listed static routes with provided configurations register: result vyos.vyos.vyos_static_routes: &id001 config: - address_families: - afi: ipv4 routes: - dest: 192.0.2.32/28 blackhole_config: distance: 2 next_hops: - forward_router_address: 192.0.2.7 - forward_router_address: 192.0.2.8 - forward_router_address: 192.0.2.9 state: replaced ``` **NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated. ### See Also: * [VyOS Platform Options](https://docs.ansible.com/ansible/latest/network/user_guide/platform_vyos.html) * [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. ## Contributing to this collection We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [VyOS collection repository](https://github.com/vyos/vyos.vyos). See [Contributing to VyOS](https://vyos.net/contribute/) for complete details. You can also join us on: - Forum - https://forum.vyos.io See the [Contributing to VyOS](https://vyos.net/contribute/) for details on contributing to Ansible. ### Code of Conduct This collection follows the Ansible project's [Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html). Please read and familiarize yourself with this document. ### Updating from resource module models Some of our modules were templated using `resource_module_builder`, but some use the newer [`cli_rm_builder`](https://github.com/ansible-network/cli_rm_builder) which tempaltes baed on in-place device information, but also uses a new network parsing engine designed to simplify and standardize the parsing of network configuration. #### Using older *resource_module_builder* modules Last build was with a slightly-modified version of resource_module_builder. This changes the calling parameters for the resources. To update the collection from the resource module models, run the following command: ```bash ansible-playbook -e rm_dest=`pwd` \ -e structure=collection \ -e collection_org=vyos \ -e collection_name=vyos \ -e model=../../../resource_module_models/models/vyos/firewall_rules/vyos_firewall_rules.yaml \ ../../../resource_module_builder/site.yml ``` #### Using *cli_rm_builder* modules The newer `cli_rm_builder` works similarly to the older `resource_module_builder`, but pulls the information directly from the `DOCUMENTATION`, `EXAMPLES` and `RETURN` blocks in the module itself. To update the collection from the `cli_rm_builder` models, run the following command: ```bash ansible-playbook -e rm_dest=`pwd` \ -e collection_org=vyos \ -e collection_name=vyos \ -e resource=bgp_address_family \ ../../../cli_rm_builder/run.yml ``` Unlike the `resource_module_builder`, the `cli_rm_builder` does not require the `model` parameter. Instead, it uses the `resource` parameter to specify the resource to build. ### Testing playbooks You can use `ANSIBLE_COLLECTIONS_PATH` to test the collection locally. For example: ``` ANSIBLE_COLLECTIONS_PATHS=~/my_dev_path ansible-playbook -i inventory.network test.yml ``` ### Integration Tests Integration tests are run using `ansible-test` and require that there be an inventory defined (you can pass this in with `--inventory `) and that the system be configured for access (recommended to use SSH keys). Additionally: - eth0 should be configured for `address dhcp` and should have an assigned address on the local network - eth1 and eth2 should be defined and uncofirgured (they'll be overwritten by the tests) - eth3 and beyond should not be present or interface-related tests will fail - when using VMs for testing, ensure that the interfaces don't use `virtio`, as it will supress some interface configurations. `e1000e` is a good choice for testing. - eth0 is also expected to show `duplex auto` and `speed auto` in the output of `show interfaces`, however others are not due to the fact that they are repeatedly deleted and recreated which causes the default values to be hidden. ## Changelogs Change logs are available [here](https://github.com/vyos/vyos.vyos/blob/main/CHANGELOG.rst). ## Release notes Release notes are available [here](https://github.com/vyos/vyos.vyos/blob/main/CHANGELOG.rst). ## Roadmap Major Version | Ansible Support | VyOS Support | Details --- | --- | --- | --- 4.1.0 | 2.15 | 1.1.2 | Final release for the 4.x series 5.0.0 | 2.16 | 1.1.2 | First relase under VyOS control as a separate collection 6.0.0 | 2.18 | 1.3.8 | *Planned* release for supporting VyOS 1.3.8+ 7.0.0 | x.xx | 1.4.x | *Prospective* release deprecating incompatible 1.3.x modules Note: - Unreleased versions are not guaranteed to be released as described. - Some modules may support a wider variety of versions depending upon the compatibility with prior versions of VyOS. - The roadmap is subject to change based on community feedback and contributions. ## More information VyOS resources - [Contributing to VyOS](https://vyos.net/contribute) - [VyOS documentation](https://docs.vyos.io/en/latest/) - [VyOS forum](https://forum.vyos.io) Ansible Resources - [Ansible network resources](https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html) - [Ansible Collection overview](https://github.com/ansible-collections/overview) - [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) - [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) - [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) ## Licensing GNU General Public License v3.0 or later. See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/changelogs/fragments/t6838_vrf.yml b/changelogs/fragments/t6838_vrf.yml new file mode 100644 index 00000000..9483e466 --- /dev/null +++ b/changelogs/fragments/t6838_vrf.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vyos_vrf - Add VRF support diff --git a/docs/vyos.vyos.vyos_vrf_module.rst b/docs/vyos.vyos.vyos_vrf_module.rst new file mode 100644 index 00000000..d05d9b34 --- /dev/null +++ b/docs/vyos.vyos.vyos_vrf_module.rst @@ -0,0 +1,1256 @@ +.. _vyos.vyos.vyos_vrf_module: + + +****************** +vyos.vyos.vyos_vrf +****************** + +**VRF resource module** + + +Version added: 1.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module manages vrf configuration on devices running Vyos + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ dictionary +
+
+ +
List of vrf configuration.
+
+
+ bind_to_all + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Enable binding services to all VRFs
+
+
+ instances + +
+ list + / elements=dictionary +
+
+ +
Virtual Routing and Forwarding instance
+
+
+ address_family + +
+ list + / elements=dictionary +
+
+ +
Address family configuration
+
+
+ afi + +
+ string +
+
+
    Choices: +
  • ipv4
  • +
  • ipv6
  • +
+
+
Address family identifier
+
+
+ disable_forwarding + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Disable forwarding for this address family
+
+
+ nht_no_resolve_via_default + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Disable next-hop resolution via default route
+
+
+ route_maps + +
+ list + / elements=dictionary +
+
+ +
List of route maps for this address family
+
+
+ protocol + +
+ string +
+
+
    Choices: +
  • any
  • +
  • babel
  • +
  • bgp
  • +
  • connected
  • +
  • eigrp
  • +
  • isis
  • +
  • kernel
  • +
  • ospf
  • +
  • rip
  • +
  • static
  • +
  • table
  • +
+
+
Protocol to which the route map applies
+
+
+ rm_name + +
+ string + / required +
+
+ +
Route map name
+
+
+ description + +
+ string +
+
+ +
Description
+
+
+ disable + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Administratively disable interface
+

aliases: disabled
+
+
+ name + +
+ string + / required +
+
+ +
VRF instance name
+
+
+ table_id + +
+ integer +
+
+ +
Routing table associated with this instance
+
+
+ vni + +
+ integer +
+
+ +
Virtual Network Identifier
+
+
+ running_config + +
+ string +
+
+ +
This option is used only with state parsed.
+
The value of this option should be the output received from the VYOS device by executing the command show configuration commands | match "set vrf".
+
The states replaced and overridden have identical behaviour for this module.
+
The state parsed reads the configuration from show configuration commands | match "set vrf" 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: +
  • deleted
  • +
  • merged ←
  • +
  • overridden
  • +
  • replaced
  • +
  • gathered
  • +
  • rendered
  • +
  • parsed
  • +
+
+
The state the configuration should be left in.
+
+
+ + +Notes +----- + +.. note:: + - Tested against vyos 1.5-stream-2025-Q1 + - This module works with connection ``network_cli``. + + + +Examples +-------- + +.. code-block:: yaml + + # # ------------------- + # # 1. Using merged + # # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue disable + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # vyos@vyos:~$ + + # # Task + # # ------------- + # - name: Merge provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # instances: + # - name: "vrf-green" + # description: "green-vrf" + # table_id: 110 + # vni: 1010 + + # Task output: + # ------------- + # "after": { + # "bind_to_all": false, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "description": "green-vrf", + # "disable": false, + # "name": "vrf-green", + # "table_id": 110, + # "vni": 1010 + # } + # ] + # }, + # "before": { + # "bind_to_all": false, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "set vrf name vrf-green table 110", + # "set vrf name vrf-green vni 1010", + # "set vrf name vrf-green description green-vrf" + # ] + + # After state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue disable + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-green description 'green-vrf' + # set vrf name vrf-green table '110' + # set vrf name vrf-green vni '1010' + # vyos@vyos:~$ + + # # ------------------- + # # 2. Using replaced + # # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + + # # Task + # # ------------- + # - name: Merge provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-blue" + # description: "blue-vrf" + # disable: false + # table_id: 100 + # vni: 1002 + # - name: "vrf-red" + # description: "red-vrf" + # disable: false + # table_id: 101 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: false + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "ospf" + # - afi: "ipv6" + # nht_no_resolve_via_default: true + # state: replaced + + # # Task output: + # # ------------- + # "after": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1002 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "ospf", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # }, + # { + # "afi": "ipv6", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": true + # } + # ], + # "description": "red-vrf", + # "disable": false, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "set vrf name vrf-blue vni 1002", + # "delete vrf name vrf-red disable", + # "set vrf name vrf-red ip protocol ospf route-map rm1", + # "delete vrf name vrf-red ip disable-forwarding", + # "set vrf name vrf-red ipv6 nht no-resolve-via-default" + # ] + + # After state: + # # ------------- + # vyos@vyos:~$ + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1002' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol ospf route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red ipv6 nht no-resolve-via-default + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + + # # ------------------- + # # 3. Using overridden + # # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # Task + # ------------- + # - name: Overridden provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-blue" + # description: "blue-vrf" + # disable: true + # table_id: 100 + # vni: 1000 + # - name: "vrf-red" + # description: "red-vrf" + # disable: true + # table_id: 101 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: false + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "rip" + # - afi: "ipv6" + # nht_no_resolve_via_default: false + # state: overridden + + # # Task output: + # # ------------- + # "after": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "delete vrf name vrf-blue", + # "commit", + # "delete vrf name vrf-red", + # "commit", + # "set vrf name vrf-blue table 100", + # "set vrf name vrf-blue vni 1000", + # "set vrf name vrf-blue description blue-vrf", + # "set vrf name vrf-blue disable", + # "set vrf name vrf-red table 101", + # "set vrf name vrf-red vni 1001", + # "set vrf name vrf-red description red-vrf", + # "set vrf name vrf-red disable", + # "set vrf name vrf-red ip protocol kernel route-map rm1", + # "set vrf name vrf-red ip protocol rip route-map rm1" + # ] + + # After state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue disable + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # 4. Using gathered + # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # Task + # ------------- + # - name: Gather provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # state: gathered + + # # Task output: + # # ------------- + # "gathered": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # } + + # After state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + + # # ------------------- + # # 5. Using deleted + # # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # # Task + # # ------------- + # - name: Replace provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: false + # instances: + # - name: "vrf-blue" + # state: deleted + + + # # Task output: + # # ------------- + # "after": { + # "bind_to_all": false, + # "instances": [ + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "delete vrf bind-to-all", + # "delete vrf name vrf-blue" + # ] + + # After state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # # ------------------- + # # 6. Using rendered + # # ------------------- + + # # Before state: + # # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + # Task + # ------------- + # - name: Render provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-green" + # description: "green-vrf" + # disabled: true + # table_id: 105 + # vni: 1000 + # - name: "vrf-amber" + # description: "amber-vrf" + # disable: false + # table_id: 111 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: true + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "ospf" + # - afi: "ipv6" + # nht_no_resolve_via_default: false + # state: rendered + + # # Task output: + # # ------------- + # "rendered": [ + # "set vrf bind-to-all", + # "set vrf name vrf-green table 105", + # "set vrf name vrf-green vni 1000", + # "set vrf name vrf-green description green-vrf", + # "set vrf name vrf-green disable", + # "set vrf name vrf-amber table 111", + # "set vrf name vrf-amber vni 1001", + # "set vrf name vrf-amber description amber-vrf", + # "set vrf name vrf-amber ip protocol kernel route-map rm1", + # "set vrf name vrf-amber ip protocol ospf route-map rm1", + # "set vrf name vrf-amber ip disable-forwarding" + # ] + + # # ------------------- + # # 7. Using parsed + # # ------------------- + + # # vrf_parsed.cfg: + # # ------------- + # set vrf bind-to-all + # set vrf name vrf1 description 'red' + # set vrf name vrf1 disable + # set vrf name vrf1 table 101 + # set vrf name vrf1 vni 501 + # set vrf name vrf2 description 'blah2' + # set vrf name vrf2 disable + # set vrf name vrf2 table 102 + # set vrf name vrf2 vni 102 + # set vrf name vrf1 ip disable-forwarding + # set vrf name vrf1 ip nht no-resolve-via-default + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol ospf route-map 'rm1' + # set vrf name vrf-red ipv6 nht no-resolve-via-default + + # Task: + # ------------- + # - name: Parse provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # running_config: "{{ lookup('file', './vrf_parsed.cfg') }}" + # state: parsed + + + # # Task output: + # # ------------- + # "parsed": { + # "bind_to_all": true, + # "instances": [ + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": true + # } + # ], + # "description": "red", + # "disable": true, + # "name": "vrf1" + # }, + # { + # "description": "blah2", + # "disable": true, + # "name": "vrf2" + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "ospf", + # "rm_name": "rm1" + # } + # ] + # }, + # { + # "afi": "ipv6", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": true + # } + # ], + # "disable": false, + # "name": "vrf-red" + # } + # ] + # } + + + +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:
+
['set system ntp server server1 dynamic', 'set system ntp server server1 prefer', 'set system ntp server server2 noselect', 'set system ntp server server2 preempt', 'set system ntp server server_add preempt']
+
+
+ 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:
+
['set system ntp server server1 dynamic', 'set system ntp server server1 prefer', 'set system ntp server server2 noselect', 'set system ntp server server2 preempt', 'set system ntp server server_add preempt']
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Evgeny Molotkov (@omnom62) diff --git a/meta/runtime.yml b/meta/runtime.yml index 58899674..36579e05 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,66 +1,68 @@ --- requires_ansible: ">=2.15.0" plugin_routing: modules: banner: redirect: vyos.vyos.vyos_banner bgp_global: redirect: vyos.vyos.vyos_bgp_global bgp_address_family: redirect: vyos.vyos.vyos_bgp_address_family command: redirect: vyos.vyos.vyos_command config: redirect: vyos.vyos.vyos_config facts: redirect: vyos.vyos.vyos_facts firewall_global: redirect: vyos.vyos.vyos_firewall_global firewall_interfaces: redirect: vyos.vyos.vyos_firewall_interfaces firewall_rules: redirect: vyos.vyos.vyos_firewall_rules hostname: redirect: vyos.vyos.vyos_hostname interfaces: redirect: vyos.vyos.vyos_interfaces l3_interfaces: redirect: vyos.vyos.vyos_l3_interfaces lag_interfaces: redirect: vyos.vyos.vyos_lag_interfaces lldp_global: redirect: vyos.vyos.vyos_lldp_global lldp_interfaces: redirect: vyos.vyos.vyos_lldp_interfaces logging: tombstone: removal_version: 6.0.0 warning_text: use vyos_logging_global instead vyos_logging: tombstone: removal_version: 6.0.0 warning_text: use vyos_logging_global instead logging_global: redirect: vyos.vyos.vyos_logging_global ntp_global: redirect: vyos.vyos.vyos_ntp_global ospfv2: redirect: vyos.vyos.vyos_ospfv2 ospfv3: redirect: vyos.vyos.vyos_ospfv3 ospf_interfaces: redirect: vyos.vyos.vyos_ospf_interfaces ping: redirect: vyos.vyos.vyos_ping prefix_lists: redirect: vyos.vyos.vyos_prefix_lists snmp_server: redirect: vyos.vyos.vyos_snmp_servers static_routes: redirect: vyos.vyos.vyos_static_routes system: redirect: vyos.vyos.vyos_system user: redirect: vyos.vyos.vyos_user vlan: redirect: vyos.vyos.vyos_vlan + vrf: + redirect: vyos.vyos.vyos_vrf diff --git a/plugins/action/vrf.py b/plugins/action/vrf.py new file mode 120000 index 00000000..331a791f --- /dev/null +++ b/plugins/action/vrf.py @@ -0,0 +1 @@ +vyos.py \ No newline at end of file diff --git a/plugins/module_utils/network/vyos/argspec/vrf/__init__.py b/plugins/module_utils/network/vyos/argspec/vrf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/argspec/vrf/vrf.py b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py new file mode 100644 index 00000000..d76871bb --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py @@ -0,0 +1,105 @@ +# -*- 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 +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the vyos_vrf module +""" + + +class VrfArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_vrf module""" + + argument_spec = { + "config": { + "type": "dict", + "options": { + "bind_to_all": {"type": "bool", "default": False}, + "instances": { + "type": "list", + "elements": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "description": {"type": "str"}, + "disable": { + "aliases": ["disabled"], + "default": False, + "type": "bool", + }, + "table_id": {"type": "int"}, + "vni": {"type": "int"}, + "address_family": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "choices": ["ipv4", "ipv6"], + }, + "disable_forwarding": {"type": "bool", "default": False}, + "nht_no_resolve_via_default": {"type": "bool", "default": False}, + "route_maps": { + "type": "list", + "elements": "dict", + "options": { + "rm_name": {"type": "str", "required": True}, + "protocol": { + "type": "str", + "choices": [ + "any", + "babel", + "bgp", + "connected", + "eigrp", + "isis", + "kernel", + "ospf", + "rip", + "static", + "table", + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "replaced", + "overridden", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + "running_config": {"type": "str"}, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/vrf/__init__.py b/plugins/module_utils/network/vyos/config/vrf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py new file mode 100644 index 00000000..61ff61b8 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py @@ -0,0 +1,243 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 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 vyos_vrf 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 copy import deepcopy + +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.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import ( + VrfTemplate, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import ( + LooseVersion, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + + +# from ansible.plugins.filter.core import combine + + +class Vrf(ResourceModule): + """ + The vyos_vrf config class + """ + + def __init__(self, module): + super(Vrf, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vrf", + tmplt=VrfTemplate(), + ) + self.parsers = [ + "bind_to_all", + ] + + def _validate_template(self): + version = get_os_version(self._module) + if LooseVersion(version) >= LooseVersion("1.4"): + self._tmplt = VrfTemplate() + else: + self._module.fail_json(msg="VRF is not supported in this version of VyOS") + + def parse(self): + """override parse to check template""" + self._validate_template() + return super().parse() + + def get_parser(self, name): + """get_parsers""" + self._validate_template() + return super().get_parser(name) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + wantd = deepcopy(self.want) + haved = deepcopy(self.have) + + # self._module.fail_json(msg="WanT: " + str(self.want) + "**** H: " + str(self.have)) + + # if state is merged, merge want onto have and then compare + if self.state in ["merged", "replaced"]: + # wantd = dict_merge(wantd, haved) + # wantd = haved | combine(wantd, recursive=True) + wantd = combine(haved, wantd, recursive=True) + # self._module.fail_json(msg="Want: " + str(wantd) + "**** H: " + str(haved)) + + # if state is deleted, delete and empty out wantd + if self.state == "deleted": + w = deepcopy(wantd) + if w == {} and haved != {}: + self.commands = ["delete vrf"] + return + for k, want in iteritems(w): + if not (k in haved and haved[k]): + del wantd[k] + else: + if isinstance(want, list): + for entry in want: + wname = entry.get("name") + haved["instances"] = [ + i for i in haved.get("instances", []) if i.get("name") != wname + ] + self.commands.append("delete vrf name {}".format(wname)) + else: + self.commands.append("delete vrf {}".format(k.replace("_", "-"))) + del wantd[k] + + if self.state == "overridden": + w = deepcopy(wantd) + h = deepcopy(haved) + for k, want in iteritems(w): + if k in haved and haved[k] != want: + if isinstance(want, list): + for entry in want: + wname = entry.get("name") + hdict = next( + (inst for inst in haved["instances"] if inst["name"] == wname), + None, + ) + if entry != hdict: + # self._module.fail_json(msg="Want: " + str(entry) + "**** H: " + str(hdict)) + haved["instances"] = [ + i for i in haved.get("instances", []) if i.get("name") != wname + ] + self.commands.append("delete vrf name {}".format(wname)) + self.commands.append("commit") + + for k, want in iteritems(wantd): + if isinstance(want, list): + self._compare_instances(want=want, have=haved.pop(k, {})) + self.compare( + parsers=self.parsers, + want={k: want}, + have={k: haved.pop(k, {})}, + ) + # self._module.fail_json(msg=self.commands) + + def _compare_instances(self, want, have): + """Compare the instances of the VRF""" + parsers = [ + "table_id", + "vni", + "description", + "disable_vrf", + ] + # self._module.fail_json(msg="want: " + str(want) + "**** have: " + str(have)) + + for entry in want: + h = {} + wname = entry.get("name") + # h = next((vrf for vrf in have if vrf["name"] == wname), {}) + h = { + k: v + for vrf in have + if vrf.get("name") == wname + for k, v in vrf.items() + if k != "address_family" + } + self.compare(parsers=parsers, want=entry, have=h) + + if "address_family" in entry: + wafi = {"name": wname, "address_family": entry.get("address_family", [])} + # hdict = next((item for item in have if item["name"] == wname), None) + hdict = next((d for d in have if d.get("name") == wname), None) + + hafi = { + "name": (hdict or {"name": wname})["name"], + "address_family": hdict.get("address_family", []) if hdict else [], + } + + # self._module.fail_json(msg="wafi: " + str(wafi) + "**** hafi: " + str(hafi)) + + self._compare_addr_family(wafi, hafi) + + def _compare_addr_family(self, want, have): + """Compare the address families of the VRF""" + afi_parsers = [ + # "address_family", + "disable_forwarding", + "disable_nht", + ] + # self._module.fail_json(msg="wAfi: " + str(want) + "**** hAfi: " + str(have)) + + wafi = self.afi_to_list(want) + hafi = self.afi_to_list(have) + + lookup = {(d["name"], d["afi"]): d for d in hafi} + pairs = [(d1, lookup.get((d1["name"], d1["afi"]), {})) for d1 in wafi] + + for wafd, hafd in pairs: + # self._module.fail_json(msg="wAfd: " + str(wafd) + "**** hAfd: " + str(hafd)) + if "route_maps" in wafd: + self._compare_route_maps(wafd, hafd) + self.compare(parsers=afi_parsers, want=wafd, have=hafd) + # self.compare(parsers=afi_parsers, want=wafi, have=hafi) + + def afi_to_list(self, data): + """Convert address family dict to list""" + + return [ + {"name": data["name"], **{**af, "afi": "ip" if af["afi"] == "ipv4" else af["afi"]}} + for af in data["address_family"] + ] + + def _compare_route_maps(self, wafd, hafd): + want_rms = wafd.get("route_maps", []) + have_rms = hafd.get("route_maps", []) + + for want in want_rms: + match = next( + ( + h + for h in have_rms + if h["rm_name"] == want["rm_name"] and h["protocol"] == want["protocol"] + ), + {}, + ) + base = {"name": wafd["name"], "afi": wafd["afi"]} + + self.compare( + parsers="route_maps", + want={**base, "route_maps": want}, + have={**base, "route_maps": match}, + ) diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 74bbda74..24c444dd 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -1,131 +1,133 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The facts class for vyos this file validates each subset of facts and selectively calls the appropriate facts gathering function """ from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( FactsBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family import ( Bgp_address_familyFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import ( Bgp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global import ( Firewall_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_interfaces.firewall_interfaces import ( Firewall_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import ( Firewall_rulesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import ( HostnameFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import ( InterfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import ( L3_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import ( Lag_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Config, Default, Neighbors, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import ( Lldp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import ( Lldp_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global import ( Logging_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ntp_global.ntp_global import ( Ntp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces import ( Ospf_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( Ospfv2Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( Ospfv3Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.prefix_lists.prefix_lists import ( Prefix_listsFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps import ( Route_mapsFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.snmp_server.snmp_server import ( Snmp_serverFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( Static_routesFacts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf import VrfFacts FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, l3_interfaces=L3_interfacesFacts, lag_interfaces=Lag_interfacesFacts, lldp_global=Lldp_globalFacts, lldp_interfaces=Lldp_interfacesFacts, static_routes=Static_routesFacts, firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, ospfv3=Ospfv3Facts, ospfv2=Ospfv2Facts, ospf_interfaces=Ospf_interfacesFacts, bgp_global=Bgp_globalFacts, bgp_address_family=Bgp_address_familyFacts, route_maps=Route_mapsFacts, prefix_lists=Prefix_listsFacts, logging_global=Logging_globalFacts, ntp_global=Ntp_globalFacts, snmp_server=Snmp_serverFacts, hostname=HostnameFacts, + vrf=VrfFacts, ) class Facts(FactsBase): """The fact class for vyos""" VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) def __init__(self, module): super(Facts, self).__init__(module) def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): """Collect the facts for vyos :param legacy_facts_type: List of legacy facts types :param resource_facts_type: List of resource fact types :param data: previously collected conf :rtype: dict :return: the facts gathered """ if self.VALID_RESOURCE_SUBSETS: self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data) if self.VALID_LEGACY_GATHER_SUBSETS: self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) return self.ansible_facts, self._warnings diff --git a/plugins/module_utils/network/vyos/facts/vrf/__init__.py b/plugins/module_utils/network/vyos/facts/vrf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/facts/vrf/vrf.py b/plugins/module_utils/network/vyos/facts/vrf/vrf.py new file mode 100644 index 00000000..ed2b80f2 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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 vyos vrf 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. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import ( + VrfTemplate, +) + + +class VrfFacts(object): + """The vyos vrf facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = VrfArgs.argument_spec + + def get_config(self, connection): + return connection.get("show configuration commands | match 'set vrf'") + + def get_config_set(self, data, connection): + """To classify the configurations beased on vrf""" + config_dict = {} + for config_line in data.splitlines(): + vrf_inst = re.search(r"set vrf name (\S+).*", config_line) + vrf_bta = re.search(r"set vrf bind-to-all", config_line) + if vrf_bta: + config_dict["bind_to_all"] = config_dict.get("bind_to_all", "") + config_line + "\n" + if vrf_inst: + config_dict[vrf_inst.group(1)] = ( + config_dict.get(vrf_inst.group(1), "") + config_line + "\n" + ) + return list(config_dict.values()) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vrf 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_facts = {} + instances = [] + vrf_parser = VrfTemplate(lines=[], module=self._module) + resources = self.get_config_set(data, connection) + for resource in resources: + vrf_parser = VrfTemplate( + lines=resource.split("\n"), + module=self._module, + ) + objs = vrf_parser.parse() + if objs: + if "bind_to_all" in objs: + vrf_facts.update(objs) + if "name" in objs: + # for key, afiv in [("address_family", "afi")]: + # if key in objs and objs[key]: + # # self._module.fail_json(msg=str(objs[key])) + # objs[key] = list(objs[key].values()) + instances.append(self._normalise_instance(objs)) + + if instances: + vrf_facts.update({"instances": instances}) + + ansible_facts["ansible_network_resources"].pop("vrf_facts", None) + facts = {"vrf": []} + + params = utils.remove_empties( + vrf_parser.validate_config( + self.argument_spec, + {"config": vrf_facts}, + redact=True, + ), + ) + + if params.get("config"): + facts["vrf"] = params["config"] + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def _normalise_instance(self, instance): + n_inst = instance.copy() + af_map = {} + + for af in instance.get("address_family", []): + afi = af.get("afi") + if not afi: + continue + + if afi not in af_map: + af_map[afi] = {"afi": afi} + + for k, v in af.items(): + if k == "afi": + continue + elif k == "route_maps": + if "route_maps" not in af_map[afi]: + af_map[afi]["route_maps"] = [] + af_map[afi]["route_maps"].extend(v) + else: + af_map[afi][k] = v + + for afi_data in af_map.values(): + if "route_maps" in afi_data: + seen = [] + deduped = [] + for item in afi_data["route_maps"]: + if item not in seen: + seen.append(item) + deduped.append(item) + afi_data["route_maps"] = deduped + + n_inst["address_family"] = list(af_map.values()) + return n_inst diff --git a/plugins/module_utils/network/vyos/rm_templates/vrf.py b/plugins/module_utils/network/vyos/rm_templates/vrf.py new file mode 100644 index 00000000..bf9fd8c6 --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/vrf.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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 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 VrfTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + prefix = {"set": "set", "remove": "delete"} + super(VrfTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module) + + # fmt: off + PARSERS = [ + { + "name": "table_id", + "getval": re.compile( + r""" + ^set + \s+vrf + \s+name + \s+(?P\S+) + \s+table + \s+'(?P\S+)' + $""", + re.VERBOSE, + ), + "setval": "vrf name {{ name }} table {{ table_id }}", + "result": { + "name": "{{ name }}", + "table_id": "{{ tid }}", + }, + }, + { + "name": "bind_to_all", + "getval": re.compile( + r""" + ^set + \svrf + \s(?Pbind-to-all) + $""", + re.VERBOSE, + ), + "setval": "vrf bind-to-all", + "result": { + "bind_to_all": "{{ True if bta is defined else {} }}", + }, + }, + { + "name": "vni", + "getval": re.compile( + r""" + ^set + \s+vrf + \s+name + \s+(?P\S+) + \s+vni + \s'(?P\S+)' + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} vni {{vni}}", + "result": { + "name": "{{ name }}", + "vni": "{{ vni }}", + }, + }, + { + "name": "description", + "getval": re.compile( + r""" + ^set + \svrf + \sname + \s(?P\S+) + \sdescription + \s(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} description {{description}}", + "result": { + "name": "{{ name }}", + "description": "{{ desc }}", + }, + }, + { + "name": "disable_vrf", + "getval": re.compile( + r""" + ^set + \svrf + \sname + \s(?P\S+) + \s(?Pdisable) + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} disable", + "compval": "disable", + "result": { + "name": "{{ name }}", + "disable": "{{ True if disable is defined }}", + }, + }, + # { + # "name": "address_family", + # "getval": re.compile( + # r""" + # ^set + # \svrf + # \sname + # \s(?P\S+) + # \s(?Pip|ipv6) + # $""", + # re.VERBOSE, + # ), + # "setval": "vrf name {{name}} {{ af }}", + # 'compval': "address_family", + # "result": { + # "name": "{{ name }}", + # "address_family": { + # '{{ "ipv4" if af == "ip" else "ipv6" }}': { + # "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', + # }, + # }, + # }, + # }, + # { + # "name": "address_family.disable_forwarding", + # "getval": re.compile( + # r""" + # ^set + # \svrf + # \sname + # \s(?P\S+) + # \s(?Pip|ipv6) + # \s(?Pdisable-forwarding) + # $""", + # re.VERBOSE, + # ), + # "setval": "vrf name {{name}} {{ afi }} disable-forwarding", + # # "compval": "address_family.ipv6.disable_forwarding", + # "result": { + # "name": "{{ name }}", + # "address_family": { + # '{{ "ipv4" if af == "ip" else "ipv6" }}': { + # "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', + # "disable_forwarding": "{{ True if df is defined }}", + # }, + # }, + # }, + # }, + { + "name": "disable_forwarding", + "getval": re.compile( + r""" + ^set + \svrf + \sname + \s(?P\S+) + \s(?Pip|ipv6) + \s(?Pdisable-forwarding) + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} {{ afi }} disable-forwarding", + "compval": "disable_forwarding", + "result": { + "name": "{{ name }}", + 'address_family': [{ + "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', + "disable_forwarding": "{{ True if df is defined }}", + }], + }, + }, + { + "name": "disable_nht", + "getval": re.compile( + r""" + ^set + \svrf + \sname + \s(?P\S+) + \s(?Pip|ipv6) + \snht + \s(?Pno-resolve-via-default) + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} {{ afi }} nht no-resolve-via-default", + "compval": "nht_no_resolve_via_default", + "result": { + "name": "{{ name }}", + "address_family": [{ + "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', + "nht_no_resolve_via_default": "{{ True if nht is defined }}", + }], + }, + }, + { + "name": "route_maps", + "getval": re.compile( + r""" + ^set + \svrf + \sname + \s(?P\S+) + \s(?Pip|ipv6) + \sprotocol + \s(?P\S+) + \sroute-map + \s'(?P\S+)' + $""", + re.VERBOSE, + ), + "setval": "vrf name {{name}} {{ afi }} protocol {{ route_maps.protocol }} route-map {{ route_maps.rm_name }}", + "compval": "route_maps", + "remval": "vrf name {{name}} {{ afi }} protocol {{ route_maps.protocol }}", + "result": { + "name": "{{ name }}", + "address_family": [{ + "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', + "route_maps": [{ + "rm_name": "{{ rm }}", + "protocol": "{{ proto }}", + }], + }], + }, + }, + ] + # fmt: on diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 4c371962..5ba48947 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,288 +1,310 @@ # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # utils from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.six import iteritems try: import ipaddress HAS_IPADDRESS = True except ImportError: HAS_IPADDRESS = False def search_obj_in_list(name, lst, key="name"): if lst: for item in lst: if item[key] == name: return item return None def get_interface_type(interface): """Gets the type of interface""" if interface.startswith("eth"): return "ethernet" elif interface.startswith("bond"): return "bonding" elif interface.startswith("vti"): return "vti" elif interface.startswith("lo"): return "loopback" elif interface.startswith("vtun"): return "openvpn" elif interface.startswith("wg"): return "wireguard" elif interface.startswith("tun"): return "tunnel" elif interface.startswith("br"): return "bridge" elif interface.startswith("dum"): return "dummy" def get_interface_with_vif(interface): """Gets virtual interface if any or return as is""" vlan = None interface_real = interface if "." in interface: interface_real, vlan = interface.split(".") if vlan is not None: interface_real = interface_real + " vif " + vlan return interface_real def dict_delete(base, comparable): """ This function generates a dict containing key, value pairs for keys that are present in the `base` dict but not present in the `comparable` dict. :param base: dict object to base the diff on :param comparable: dict object to compare against base :returns: new dict object with key, value pairs that needs to be deleted. """ to_delete = dict() for key in base: if isinstance(base[key], dict): sub_diff = dict_delete(base[key], comparable.get(key, {})) if sub_diff: to_delete[key] = sub_diff else: if key not in comparable: to_delete[key] = base[key] return to_delete def diff_list_of_dicts(want, have): diff = [] set_w = set(tuple(d.items()) for d in want) set_h = set(tuple(d.items()) for d in have) difference = set_w.difference(set_h) for element in difference: diff.append(dict((x, y) for x, y in element)) return diff def get_lst_diff_for_dicts(want, have, lst): """ This function generates a list containing values that are only in want and not in list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the diff on :return: new list object with values which are only in want. """ if not have: diff = want.get(lst) or [] else: want_elements = want.get(lst) or {} have_elements = have.get(lst) or {} diff = list_diff_want_only(want_elements, have_elements) return diff def get_lst_same_for_dicts(want, have, lst): """ This function generates a list containing values that are common for list in want and list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the comparison on :return: new list object with values which are common in want and have. """ diff = None if want and have: want_list = want.get(lst) or {} have_list = have.get(lst) or {} diff = [i for i in want_list and have_list if i in have_list and i in want_list] return diff def list_diff_have_only(want_list, have_list): """ This function generated the list containing values that are only in have list. :param want_list: :param have_list: :return: new list with values which are only in have list """ if have_list and not want_list: diff = have_list elif not have_list: diff = None else: diff = [i for i in have_list + want_list if i in have_list and i not in want_list] return diff def list_diff_want_only(want_list, have_list): """ This function generated the list containing values that are only in want list. :param want_list: :param have_list: :return: new list with values which are only in want list """ if have_list and not want_list: diff = None elif not have_list: diff = want_list else: diff = [i for i in have_list + want_list if i in want_list and i not in have_list] return diff def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2): """ This function return the dict object if it exist in list. :param d_val1: :param d_val2: :param lst: :param key1: :param key2: :return: """ obj = next( (item for item in lst if item[key1] == d_val1 and item[key2] == d_val2), None, ) if obj: return obj else: return None def key_value_in_dict(have_key, have_value, want_dict): """ This function checks whether the key and values exist in dict :param have_key: :param have_value: :param want_dict: :return: """ for key, value in iteritems(want_dict): if key == have_key and value == have_value: return True return False def is_dict_element_present(dict, key): """ This function checks whether the key is present in dict. :param dict: :param key: :return: """ for item in dict: if item == key: return True return False def get_ip_address_version(address): """ This function returns the version of IP address :param address: IP address :return: """ if not HAS_IPADDRESS: raise Exception(missing_required_lib("ipaddress")) try: address = unicode(address) except NameError: address = str(address) version = ipaddress.ip_address(address.split("/")[0]).version return version def get_route_type(address): """ This function returns the route type based on IP address :param address: :return: """ version = get_ip_address_version(address) if version == 6: return "route6" elif version == 4: return "route" def _bool_to_str(val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return "enable" if str(val) == "True" else "disable" if str(val) == "False" else val def _is_w_same(w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False def in_target_not_none(h, key): """ This function checks whether the target exist,key present in target config, and the value is not None. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h and h[key] is not None else False + + +def combine(a, b, recursive=False, list_merge="replace"): + """ + Merge two dictionaries (shallow or deep). + :param a: dict + :param b: dict + :param recursive: bool, deep merge + :param list_merge: str, only 'replace' is supported (default Ansible behavior) + """ + if not isinstance(a, dict) or not isinstance(b, dict): + raise ValueError("combine expects two dictionaries") + + result = a.copy() + + for k, v in b.items(): + if recursive and k in result and isinstance(result[k], dict) and isinstance(v, dict): + result[k] = combine(result[k], v, recursive=True, list_merge=list_merge) + else: + result[k] = v + + return result diff --git a/plugins/modules/vyos_vrf.py b/plugins/modules/vyos_vrf.py new file mode 100644 index 00000000..314aeb80 --- /dev/null +++ b/plugins/modules/vyos_vrf.py @@ -0,0 +1,940 @@ +#!/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 vyos_vrf +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: vyos_vrf +version_added: 1.0.0 +short_description: VRF resource module +description: +- This module manages vrf configuration on devices running Vyos +author: +- Evgeny Molotkov (@omnom62) +notes: +- Tested against vyos 1.5-stream-2025-Q1 +- This module works with connection C(network_cli). +options: + config: + description: List of vrf configuration. + type: dict + suboptions: + bind_to_all: + default: false + description: Enable binding services to all VRFs + type: bool + instances: + description: Virtual Routing and Forwarding instance + type: list + elements: dict + suboptions: + name: + description: VRF instance name + required: true + type: str + description: + description: Description + type: str + disable: + default: false + description: Administratively disable interface + type: bool + aliases: ['disabled'] + table_id: + description: Routing table associated with this instance + type: int + vni: + description: Virtual Network Identifier + type: int + address_family: + type: list + elements: dict + description: Address family configuration + suboptions: + afi: + description: Address family identifier + type: str + choices: ['ipv4', 'ipv6'] + disable_forwarding: + default: False + description: Disable forwarding for this address family + type: bool + nht_no_resolve_via_default: + default: False + description: Disable next-hop resolution via default route + type: bool + route_maps: + description: List of route maps for this address family + type: list + elements: dict + suboptions: + rm_name: + description: Route map name + type: str + required: true + protocol: + description: Protocol to which the route map applies + type: str + choices: + - any + - babel + - bgp + - connected + - eigrp + - isis + - kernel + - ospf + - rip + - static + - table + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VYOS device by + executing the command B(show configuration commands | match "set vrf"). + - The states I(replaced) and I(overridden) have identical + behaviour for this module. + - The state I(parsed) reads the configuration from C(show configuration commands | match "set vrf") option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - deleted + - merged + - overridden + - replaced + - gathered + - rendered + - parsed + default: merged +""" + +EXAMPLES = """ +# # ------------------- +# # 1. Using merged +# # ------------------- + +# # Before state: +# # ------------- +# vyos@vyos:~$ show configuration commands | match 'set vrf' +# set vrf name vrf-blue description 'blue-vrf' +# set vrf name vrf-blue disable +# set vrf name vrf-blue table '100' +# set vrf name vrf-blue vni '1000' +# vyos@vyos:~$ + +# # Task +# # ------------- + # - name: Merge provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # instances: + # - name: "vrf-green" + # description: "green-vrf" + # table_id: 110 + # vni: 1010 + +# Task output: +# ------------- + # "after": { + # "bind_to_all": false, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "description": "green-vrf", + # "disable": false, + # "name": "vrf-green", + # "table_id": 110, + # "vni": 1010 + # } + # ] + # }, + # "before": { + # "bind_to_all": false, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "set vrf name vrf-green table 110", + # "set vrf name vrf-green vni 1010", + # "set vrf name vrf-green description green-vrf" + # ] + +# After state: +# # ------------- +# vyos@vyos:~$ show configuration commands | match 'set vrf' +# set vrf name vrf-blue description 'blue-vrf' +# set vrf name vrf-blue disable +# set vrf name vrf-blue table '100' +# set vrf name vrf-blue vni '1000' +# set vrf name vrf-green description 'green-vrf' +# set vrf name vrf-green table '110' +# set vrf name vrf-green vni '1010' +# vyos@vyos:~$ + +# # ------------------- +# # 2. Using replaced +# # ------------------- + +# # Before state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + +# # Task +# # ------------- + # - name: Merge provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-blue" + # description: "blue-vrf" + # disable: false + # table_id: 100 + # vni: 1002 + # - name: "vrf-red" + # description: "red-vrf" + # disable: false + # table_id: 101 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: false + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "ospf" + # - afi: "ipv6" + # nht_no_resolve_via_default: true + # state: replaced + +# # Task output: +# # ------------- + # "after": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1002 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "ospf", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # }, + # { + # "afi": "ipv6", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": true + # } + # ], + # "description": "red-vrf", + # "disable": false, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "set vrf name vrf-blue vni 1002", + # "delete vrf name vrf-red disable", + # "set vrf name vrf-red ip protocol ospf route-map rm1", + # "delete vrf name vrf-red ip disable-forwarding", + # "set vrf name vrf-red ipv6 nht no-resolve-via-default" + # ] + +# After state: +# # ------------- + # vyos@vyos:~$ + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1002' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol ospf route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red ipv6 nht no-resolve-via-default + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + +# # ------------------- +# # 3. Using overridden +# # ------------------- + +# # Before state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# Task +# ------------- + # - name: Overridden provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-blue" + # description: "blue-vrf" + # disable: true + # table_id: 100 + # vni: 1000 + # - name: "vrf-red" + # description: "red-vrf" + # disable: true + # table_id: 101 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: false + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "rip" + # - afi: "ipv6" + # nht_no_resolve_via_default: false + # state: overridden + +# # Task output: +# # ------------- + # "after": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": true, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": false, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "delete vrf name vrf-blue", + # "commit", + # "delete vrf name vrf-red", + # "commit", + # "set vrf name vrf-blue table 100", + # "set vrf name vrf-blue vni 1000", + # "set vrf name vrf-blue description blue-vrf", + # "set vrf name vrf-blue disable", + # "set vrf name vrf-red table 101", + # "set vrf name vrf-red vni 1001", + # "set vrf name vrf-red description red-vrf", + # "set vrf name vrf-red disable", + # "set vrf name vrf-red ip protocol kernel route-map rm1", + # "set vrf name vrf-red ip protocol rip route-map rm1" + # ] + +# After state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue disable + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# 4. Using gathered +# ------------------- + +# # Before state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# Task +# ------------- +# - name: Gather provided configuration with device configuration +# vyos.vyos.vyos_vrf: +# config: +# state: gathered + +# # Task output: +# # ------------- + # "gathered": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # } + +# After state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + + +# # ------------------- +# # 5. Using deleted +# # ------------------- + +# # Before state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf bind-to-all + # set vrf name vrf-blue description 'blue-vrf' + # set vrf name vrf-blue table '100' + # set vrf name vrf-blue vni '1000' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# # Task +# # ------------- +# - name: Replace provided configuration with device configuration +# vyos.vyos.vyos_vrf: +# config: +# bind_to_all: false +# instances: +# - name: "vrf-blue" +# state: deleted + + +# # Task output: +# # ------------- + # "after": { + # "bind_to_all": false, + # "instances": [ + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "before": { + # "bind_to_all": true, + # "instances": [ + # { + # "description": "blue-vrf", + # "disable": false, + # "name": "vrf-blue", + # "table_id": 100, + # "vni": 1000 + # }, + # { + # "address_family": [ + # { + # "afi": "ipv4", + # "disable_forwarding": true, + # "nht_no_resolve_via_default": false, + # "route_maps": [ + # { + # "protocol": "kernel", + # "rm_name": "rm1" + # }, + # { + # "protocol": "rip", + # "rm_name": "rm1" + # } + # ] + # } + # ], + # "description": "red-vrf", + # "disable": true, + # "name": "vrf-red", + # "table_id": 101, + # "vni": 1001 + # } + # ] + # }, + # "changed": true, + # "commands": [ + # "delete vrf bind-to-all", + # "delete vrf name vrf-blue" + # ] + +# After state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# # ------------------- +# # 6. Using rendered +# # ------------------- + +# # Before state: +# # ------------- + # vyos@vyos:~$ show configuration commands | match 'set vrf' + # set vrf name vrf-red description 'red-vrf' + # set vrf name vrf-red disable + # set vrf name vrf-red ip disable-forwarding + # set vrf name vrf-red ip protocol kernel route-map 'rm1' + # set vrf name vrf-red ip protocol rip route-map 'rm1' + # set vrf name vrf-red table '101' + # set vrf name vrf-red vni '1001' + # vyos@vyos:~$ + +# Task +# ------------- + # - name: Render provided configuration with device configuration + # vyos.vyos.vyos_vrf: + # config: + # bind_to_all: true + # instances: + # - name: "vrf-green" + # description: "green-vrf" + # disabled: true + # table_id: 105 + # vni: 1000 + # - name: "vrf-amber" + # description: "amber-vrf" + # disable: false + # table_id: 111 + # vni: 1001 + # address_family: + # - afi: "ipv4" + # disable_forwarding: true + # route_maps: + # - rm_name: "rm1" + # protocol: "kernel" + # - rm_name: "rm1" + # protocol: "ospf" + # - afi: "ipv6" + # nht_no_resolve_via_default: false + # state: rendered + +# # Task output: +# # ------------- + # "rendered": [ + # "set vrf bind-to-all", + # "set vrf name vrf-green table 105", + # "set vrf name vrf-green vni 1000", + # "set vrf name vrf-green description green-vrf", + # "set vrf name vrf-green disable", + # "set vrf name vrf-amber table 111", + # "set vrf name vrf-amber vni 1001", + # "set vrf name vrf-amber description amber-vrf", + # "set vrf name vrf-amber ip protocol kernel route-map rm1", + # "set vrf name vrf-amber ip protocol ospf route-map rm1", + # "set vrf name vrf-amber ip disable-forwarding" + # ] + +# # ------------------- +# # 7. Using parsed +# # ------------------- + +# # vrf_parsed.cfg: +# # ------------- +# set vrf bind-to-all +# set vrf name vrf1 description 'red' +# set vrf name vrf1 disable +# set vrf name vrf1 table 101 +# set vrf name vrf1 vni 501 +# set vrf name vrf2 description 'blah2' +# set vrf name vrf2 disable +# set vrf name vrf2 table 102 +# set vrf name vrf2 vni 102 +# set vrf name vrf1 ip disable-forwarding +# set vrf name vrf1 ip nht no-resolve-via-default +# set vrf name vrf-red ip protocol kernel route-map 'rm1' +# set vrf name vrf-red ip protocol ospf route-map 'rm1' +# set vrf name vrf-red ipv6 nht no-resolve-via-default + +# Task: +# ------------- +# - name: Parse provided configuration with device configuration +# vyos.vyos.vyos_vrf: +# running_config: "{{ lookup('file', './vrf_parsed.cfg') }}" +# state: parsed + + +# # Task output: +# # ------------- +# "parsed": { +# "bind_to_all": true, +# "instances": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "disable_forwarding": true, +# "nht_no_resolve_via_default": true +# } +# ], +# "description": "red", +# "disable": true, +# "name": "vrf1" +# }, +# { +# "description": "blah2", +# "disable": true, +# "name": "vrf2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "disable_forwarding": false, +# "nht_no_resolve_via_default": false, +# "route_maps": [ +# { +# "protocol": "kernel", +# "rm_name": "rm1" +# }, +# { +# "protocol": "ospf", +# "rm_name": "rm1" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "disable_forwarding": false, +# "nht_no_resolve_via_default": true +# } +# ], +# "disable": false, +# "name": "vrf-red" +# } +# ] +# } +""" + +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: + - set system ntp server server1 dynamic + - set system ntp server server1 prefer + - set system ntp server server2 noselect + - set system ntp server server2 preempt + - set system ntp server server_add preempt +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - set system ntp server server1 dynamic + - set system ntp server server1 prefer + - set system ntp server server2 noselect + - set system ntp server server2 preempt + - set system ntp server server_add preempt +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.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vrf.vrf import Vrf + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=VrfArgs.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(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/vyos_vrf/defaults/main.yaml b/tests/integration/targets/vyos_vrf/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_vrf/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_vrf/tasks/cli.yaml b/tests/integration/targets/vyos_vrf/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- 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 }}" + +- 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/vyos_vrf/tasks/main.yaml b/tests/integration/targets/vyos_vrf/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Run CLI tests + ansible.builtin.include_tasks: cli.yaml + tags: + - network_cli diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_add_rm.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_add_rm.yaml new file mode 100644 index 00000000..ddbed78e --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_add_rm.yaml @@ -0,0 +1,12 @@ +--- +- ansible.builtin.include_tasks: _remove_rm.yaml + +- name: Add Route Map + ansible.netcommon.cli_config: + config: |- + set policy route-map rm1 rule 10 action 'permit' + set policy route-map rm1 rule 10 match peer '192.0.2.32' + set policy route-map rm1 rule 10 set aggregator as '100' + set policy route-map rm1 rule 10 set as-path exclude '111' + set policy route-map rm1 rule 10 set large-community none + set policy route-map rm1 rule 10 set metric '5' diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg new file mode 100644 index 00000000..b0eb373a --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg @@ -0,0 +1,14 @@ +set vrf bind-to-all +set vrf name vrf1 description 'Parser Test VRF 1' +set vrf name vrf1 disable +set vrf name vrf1 table 101 +set vrf name vrf1 vni 501 +set vrf name vrf2 description 'Parser Test VRF 2' +set vrf name vrf2 disable +set vrf name vrf2 table 102 +set vrf name vrf2 vni 102 +set vrf name vrf1 ip disable-forwarding +set vrf name vrf1 ip nht no-resolve-via-default +set vrf name vrf-red ip protocol kernel route-map 'rm1' +set vrf name vrf-red ip protocol ospf route-map 'rm1' +set vrf name vrf-red ipv6 nht no-resolve-via-default diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml new file mode 100644 index 00000000..9e6253ee --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_populate.yaml @@ -0,0 +1,10 @@ +--- +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _add_rm.yaml + +- name: Setup + vyos.vyos.vyos_config: + lines: "{{ populate_config }}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..d909adfd --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_remove_config.yaml @@ -0,0 +1,9 @@ +--- +- name: Remove route-maps and VRF configuration + vyos.vyos.vyos_config: + lines: + - delete policy route-map rm1 + - delete vrf + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrf/tests/cli/_remove_rm.yaml b/tests/integration/targets/vyos_vrf/tests/cli/_remove_rm.yaml new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml new file mode 100644 index 00000000..637e38ae --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml @@ -0,0 +1,35 @@ +--- +- debug: + msg: START vyos_vrf deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Delete the provided configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + state: deleted + + - assert: + that: + # - result.commands|length == 2 + - result.changed == true + - result.commands|symmetric_difference(deleted.commands) == [] + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}" + + # - name: Delete the existing configuration with the provided running configuration (IDEMPOTENT) + # register: result + # vyos.vyos.vyos_vrf: *id001 + + # - name: Assert that the previous task was idempotent + # assert: + # that: + # - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vrf/tests/cli/empty_config.yaml new file mode 100644 index 00000000..ba1dab0d --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/empty_config.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_vrf empty_config integration tests on connection={{ ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrf: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrf: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrf: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrf: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrf: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vrf/tests/cli/gathered.yaml new file mode 100644 index 00000000..7ea7b286 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/gathered.yaml @@ -0,0 +1,22 @@ +--- +- debug: + msg: START vyos_vrf gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_vrf: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == false + - result.gathered|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml b/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml new file mode 100644 index 00000000..dd747f98 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: START vyos_vrf merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + # bind_to_all: true + instances: + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + # - result.commands|length == 13 + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml new file mode 100644 index 00000000..89120ec5 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml @@ -0,0 +1,40 @@ +--- +- debug: + msg: START vyos_vrf overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Override the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: True + table_id: 103 + vni: 1002 + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + # - result.commands|length == 8 + - result.changed == true + - result.commands|symmetric_difference(overridden.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vrf/tests/cli/parsed.yaml new file mode 100644 index 00000000..067db2f3 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/parsed.yaml @@ -0,0 +1,16 @@ +--- +- debug: + msg: START vyos_vrf parsed integration tests on connection={{ ansible_connection }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_vrf: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- assert: + that: + - result.changed == false + - result.parsed|symmetric_difference(merged.after) == [] + +- include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml new file mode 100644 index 00000000..b72ec23c --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_route_maps rendered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_vrf: + config: + bind_to_all: true + instances: + - name: vrf-green + description: green-vrf + disabled: true + table_id: 105 + vni: 1000 + - name: vrf-amber + description: amber-vrf + disable: false + table_id: 111 + vni: 1001 + address_family: + - afi: ipv4 + disable_forwarding: true + route_maps: + - rm_name: rm1 + protocol: kernel + - rm_name: rm1 + protocol: ospf + - afi: ipv6 + nht_no_resolve_via_default: false + state: rendered + + - assert: + that: + - result.changed == false + - result.rendered|symmetric_difference(rendered.commands) == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml new file mode 100644 index 00000000..790d485b --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml @@ -0,0 +1,47 @@ +--- +- debug: + msg: START vyos_vrf replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_vrf: &id001 + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: true + table_id: 100 + vni: 1000 + - name: "vrf-red" + description: "Vermillion_VRF" + disable: false + table_id: 101 + vni: 1011 + address_family: + - afi: "ipv6" + nht_no_resolve_via_default: false + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - result.changed == true + - result.commands|symmetric_difference(replaced.commands) == [] + - result.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_vrf: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml b/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml new file mode 100644 index 00000000..53b1d4ff --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml @@ -0,0 +1,59 @@ +--- +- debug: + msg: START vyos_vrf merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: baseconfig + vyos.vyos.vyos_vrf: + config: + bind_to_all: true + instances: + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - baseconfig.changed == true + - baseconfig.commands|symmetric_difference(merged.commands) == [] + - baseconfig.after|symmetric_difference(ansible_facts['network_resources']['vrf']) == [] + + - name: Merge the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_vrf: + config: + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: true + table_id: 100 + vni: 1000 + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + address_family: + - afi: "ipv4" + disable_forwarding: true + state: merged + + - name: Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_vrf: + config: "{{ ansible_facts['network_resources']['vrf'] }}" + state: overridden + + - name: Assert that config was reverted + assert: + that: baseconfig.after == revert.after + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrf/vars/main.yaml b/tests/integration/targets/vyos_vrf/vars/main.yaml new file mode 100644 index 00000000..c80c370b --- /dev/null +++ b/tests/integration/targets/vyos_vrf/vars/main.yaml @@ -0,0 +1,85 @@ +--- +merged: + before: + bind_to_all: false + commands: + - set vrf name vrf-green table 110 + - set vrf name vrf-green vni 1010 + - set vrf name vrf-green description green-vrf + after: + bind_to_all: true + instances: + - name: "vrf-blue" + description: "blue-vrf" + disable: false + table_id: 100 + vni: 1000 + - name: "vrf-red" + description: "red-vrf" + disable: true + table_id: 101 + vni: 1001 + address_family: + - afi: "ipv4" + disable_forwarding: true + route_maps: + - rm_name: "rm1" + protocol: "kernel" + - rm_name: "rm1" + protocol: "rip" + - afi: "ipv6" + nht_no_resolve_via_default: true + +replaced: + commands: + - delete vrf bind-to-all + - set vrf name vrf-blue disable + - set vrf name vrf-red vni 1011 + - set vrf name vrf-red description Vermillion_VRF + - delete vrf name vrf-red disable + - delete vrf name vrf-red ipv6 nht no-resolve-via-default + after: [] + +overridden: + commands: + - delete vrf name vrf-blue + - commit + - set vrf name vrf-blue table 103 + - set vrf name vrf-blue vni 1002 + - set vrf name vrf-blue description blue-vrf + - set vrf name vrf-blue disable + - delete vrf bind-to-all + after: [] + +deleted: + commands: + - delete vrf + after: [] + +rendered: + commands: + - set vrf bind-to-all + - set vrf name vrf-green table 105 + - set vrf name vrf-green vni 1000 + - set vrf name vrf-green description green-vrf + - set vrf name vrf-green disable + - set vrf name vrf-amber table 111 + - set vrf name vrf-amber vni 1001 + - set vrf name vrf-amber description amber-vrf + - set vrf name vrf-amber ip protocol kernel route-map rm1 + - set vrf name vrf-amber ip protocol ospf route-map rm1 + - set vrf name vrf-amber ip disable-forwarding + +populate_config: + - set vrf bind-to-all + - set vrf name vrf-blue description 'blue-vrf' + - set vrf name vrf-blue table '100' + - set vrf name vrf-blue vni '1000' + - set vrf name vrf-red description 'red-vrf' + - set vrf name vrf-red disable + - set vrf name vrf-red ip disable-forwarding + - set vrf name vrf-red ip protocol kernel route-map 'rm1' + - set vrf name vrf-red ip protocol rip route-map 'rm1' + - set vrf name vrf-red ipv6 nht no-resolve-via-default + - set vrf name vrf-red table '101' + - set vrf name vrf-red vni '1001' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg new file mode 100644 index 00000000..57bf0e32 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg @@ -0,0 +1,12 @@ +set vrf bind-to-all +set vrf name vrf-blue description 'blue-vrf' +set vrf name vrf-blue table '100' +set vrf name vrf-blue vni '1000' +set vrf name vrf-red description 'red-vrf' +set vrf name vrf-red disable +set vrf name vrf-red ip disable-forwarding +set vrf name vrf-red ip protocol kernel route-map 'rm1' +set vrf name vrf-red ip protocol rip route-map 'rm1' +set vrf name vrf-red ipv6 nht no-resolve-via-default +set vrf name vrf-red table '101' +set vrf name vrf-red vni '1001' diff --git a/tests/unit/modules/network/vyos/test_vyos_vrf.py b/tests/unit/modules/network/vyos/test_vyos_vrf.py new file mode 100644 index 00000000..03ff71ec --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vrf.py @@ -0,0 +1,507 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vrf +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVrfModule(TestVyosModule): + module = vyos_vrf + + def setUp(self): + super(TestVyosVrfModule, self).setUp() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf.VrfFacts.get_config", + ) + + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vrf.vrf.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.5" + self.maxDiff = None + + def tearDown(self): + super(TestVyosVrfModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_vrf_config.cfg" + + def load_from_file(*args, **kwargs): + output = load_fixture(filename) + return output + + self.execute_show_command.side_effect = load_from_file + + def test_vrf_merged_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="kernel"), + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=True, + ), + ], + ), + ], + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrf_merged(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-green", + description="green-vrf", + table_id=110, + vni=1010, + ), + ], + ), + state="merged", + ), + ) + + commands = [ + "set vrf name vrf-green table 110", + "set vrf name vrf-green vni 1010", + "set vrf name vrf-green description green-vrf", + ] + + self.execute_module(changed=True, commands=commands) + + def test_vrf_replaced(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf-replaced", + disable=True, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=False, + route_maps=[ + dict(rm_name="rm1", protocol="kernel"), + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=False, + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "set vrf name vrf-blue description blue-vrf-replaced", + "set vrf name vrf-blue disable", + "delete vrf name vrf-red ip disable-forwarding", + "delete vrf name vrf-red ipv6 nht no-resolve-via-default", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="kernel"), + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=True, + ), + ], + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrf_overridden(self): + set_module_args( + dict( + config=dict( + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=True, + table_id=103, + vni=1002, + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "delete vrf name vrf-blue", + "commit", + "set vrf name vrf-blue table 103", + "set vrf name vrf-blue vni 1002", + "set vrf name vrf-blue description blue-vrf", + "set vrf name vrf-blue disable", + "delete vrf bind-to-all", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + description="blue-vrf", + disable=False, + table_id=100, + vni=1000, + ), + dict( + name="vrf-red", + description="red-vrf", + disable=True, + table_id=101, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="kernel"), + dict(rm_name="rm1", protocol="rip"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=True, + ), + ], + ), + ], + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrf_rendered(self): + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-green", + description="green-vrf", + disabled=True, + table_id=105, + vni=1000, + ), + dict( + name="vrf-amber", + description="amber-vrf", + disable=False, + table_id=111, + vni=1001, + address_family=[ + dict( + afi="ipv4", + disable_forwarding=True, + route_maps=[ + dict(rm_name="rm1", protocol="kernel"), + dict(rm_name="rm1", protocol="ospf"), + ], + ), + dict( + afi="ipv6", + nht_no_resolve_via_default=False, + ), + ], + ), + ], + ), + state="rendered", + ), + ) + rendered_commands = [ + "set vrf bind-to-all", + "set vrf name vrf-green table 105", + "set vrf name vrf-green vni 1000", + "set vrf name vrf-green description green-vrf", + "set vrf name vrf-green disable", + "set vrf name vrf-amber table 111", + "set vrf name vrf-amber vni 1001", + "set vrf name vrf-amber description amber-vrf", + "set vrf name vrf-amber ip protocol kernel route-map rm1", + "set vrf name vrf-amber ip protocol ospf route-map rm1", + "set vrf name vrf-amber ip disable-forwarding", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_commands), + result["rendered"], + ) + + def test_vrf_parsed(self): + commands = ( + "set vrf bind-to-all", + "set vrf name vrf1 description 'red'", + "set vrf name vrf1 disable", + "set vrf name vrf1 table 101", + "set vrf name vrf1 vni 501", + "set vrf name vrf2 description 'green'", + "set vrf name vrf2 disable", + "set vrf name vrf2 table 102", + "set vrf name vrf2 vni 102", + "set vrf name vrf1 ip disable-forwarding", + "set vrf name vrf1 ip nht no-resolve-via-default", + "set vrf name vrf-red ip protocol kernel route-map 'rm1'", + "set vrf name vrf-red ip protocol ospf route-map 'rm1'", + "set vrf name vrf-red ipv6 nht no-resolve-via-default", + ) + parsed_str = "\n".join(commands) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = { + "bind_to_all": True, + "instances": [ + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": True, + "nht_no_resolve_via_default": True, + }, + ], + "description": "red", + "disable": True, + "name": "vrf1", + }, + { + "description": "green", + "disable": True, + "name": "vrf2", + }, + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": False, + "nht_no_resolve_via_default": False, + "route_maps": [ + { + "protocol": "kernel", + "rm_name": "rm1", + }, + { + "protocol": "ospf", + "rm_name": "rm1", + }, + ], + }, + { + "afi": "ipv6", + "disable_forwarding": False, + "nht_no_resolve_via_default": True, + }, + ], + "disable": False, + "name": "vrf-red", + }, + ], + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_vrf_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = { + "bind_to_all": True, + "instances": [ + { + "description": "blue-vrf", + "disable": False, + "name": "vrf-blue", + "table_id": 100, + "vni": 1000, + }, + { + "address_family": [ + { + "afi": "ipv4", + "disable_forwarding": True, + "nht_no_resolve_via_default": False, + "route_maps": [ + { + "protocol": "kernel", + "rm_name": "rm1", + }, + { + "protocol": "rip", + "rm_name": "rm1", + }, + ], + }, + { + "afi": "ipv6", + "disable_forwarding": False, + "nht_no_resolve_via_default": True, + }, + ], + "description": "red-vrf", + "disable": True, + "name": "vrf-red", + "table_id": 101, + "vni": 1001, + }, + ], + } + self.assertEqual(gathered_list, result["gathered"]) + + def test_vrf_deleted(self): + # Delete the instances and global setting bind_to_all + set_module_args( + dict( + config=dict( + bind_to_all=True, + instances=[ + dict( + name="vrf-blue", + ), + ], + ), + state="deleted", + ), + ) + commands = [ + "delete vrf bind-to-all", + "delete vrf name vrf-blue", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrf_all_deleted(self): + set_module_args( + dict( + state="deleted", + ), + ) + commands = [ + "delete vrf", + ] + self.execute_module(changed=True, commands=commands)