diff --git a/PR408_README.md b/PR408_README.md new file mode 100644 index 000000000..0423b56fb --- /dev/null +++ b/PR408_README.md @@ -0,0 +1,163 @@ +# ๐Ÿ“ฆ Pull Request Documentation + +## ๐Ÿงพ Summary + + + +This is a new Ansible module to manage VyOS VRF configuration as requested by https://vyos.dev/T6836 + +--- + +## ๐Ÿ” Implementation + + + +The module implements a set of VyOS 1.4.2 and 1.5.x commands related to VRF configuration, described in https://docs.vyos.io/en/1.4/configuration/vrf/index.html +The module allows to configure a global attribute (bind-all), as well as, VRF instance configuration. + +The structure of the parameters is depicted below +![Diagram](pr408-diagram.png) + +The module supports the following states: + +- "deleted" +- "merged" +- "replaced" +- "overridden" +- "gathered" +- "rendered" +- "parsed" + +--- + +## ๐Ÿ“ Examples + +### Replaced (to demo individual attribute processing) + +Initial coonfiguration + +``` + - 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' + - set vrf name vrf-pink table 111 + - set vrf name vrf-pink vni 1111 + - set vrf name vrf-pink description pink-vrf + - set vrf name vrf-pink protocols bgp system-as 65000 + - set vrf name vrf-pink protocols bgp neighbor 192.0.2.1 remote-as 65002 + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 passive + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 remote-as 400 + - set vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' + - set vrf name vrf-pink protocols ospf max-metric router-lsa administrative + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-shutdown 10 + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-startup 10 + - set vrf name vrf-pink protocols ospf default-information originate always + - set vrf name vrf-pink protocols ospf default-information originate metric 10 + - set vrf name vrf-pink protocols ospf default-information originate metric-type 2 + - set vrf name vrf-pink protocols ospf mpls-te router-address '192.0.11.12' + - set vrf name vrf-pink protocols ospf auto-cost reference-bandwidth '2' + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 priority 2 + - set vrf name vrf-pink protocols ospf redistribute bgp + - set vrf name vrf-pink protocols ospf redistribute bgp metric 10 + - set vrf name vrf-pink protocols ospf redistribute bgp metric-type 2 + - set vrf name vrf-pink protocols ospf parameters router-id '192.0.1.1' + - set vrf name vrf-pink protocols ospf parameters rfc1583-compatibility + - set vrf name vrf-pink protocols ospf parameters abr-type 'cisco' + - set vrf name vrf-pink protocols ospf area '2' + - set vrf name vrf-pink protocols ospf area 2 area-type normal + - set vrf name vrf-pink protocols ospf area 2 authentication plaintext-password + - set vrf name vrf-pink protocols ospf area 2 shortcut enable + - set vrf name vrf-pink protocols ospf area '3' + - set vrf name vrf-pink protocols ospf area 3 area-type nssa + - set vrf name vrf-pink protocols ospf area '4' + - set vrf name vrf-pink protocols ospf area 4 area-type stub default-cost 20 + - set vrf name vrf-pink protocols ospf area 4 network 192.0.2.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set vrf name vrf-pink protocols ospfv3 area '2' + - set vrf name vrf-pink protocols ospfv3 area 2 export-list export1 + - set vrf name vrf-pink protocols ospfv3 area 2 import-list import1 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db10::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db20::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db30::/32 + - set vrf name vrf-pink protocols ospfv3 area '3' + - set vrf name vrf-pink protocols ospfv3 area 3 range 2001:db40::/32 + - set vrf name vrf-pink protocols ospfv3 parameters router-id '192.0.2.10' + - set vrf name vrf-pink protocols ospfv3 redistribute bgp + - set vrf name vrf-pink protocols static route 192.0.2.0/24 + - set vrf name vrf-pink protocols static route 192.0.2.0/24 blackhole distance '10' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.1' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.2' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 + - set vrf name vrf-pink protocols static route6 2001:db8::/32 blackhole distance '20' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 next-hop '2001:db8::1' +``` + +Module configuration + +```yaml +vyos.vyos.vyos_vrf: + 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 + - name: "vrf-pink" + table_id: 111 + protocols: + ospf: + default_information: + originate: + always: true + metric: 20 + metric_type: 1 + state: replaced +``` + +Will result in + +```` +- 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 +- delete vrf name vrf-pink vni 1111 +- delete vrf name vrf-pink description pink-vrf +- delete vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' +- delete vrf name vrf-pink protocols ospf auto-cost +- delete vrf name vrf-pink protocols ospf max-metric +- delete vrf name vrf-pink protocols ospf mpls-te +- delete vrf name vrf-pink protocols ospf area +- delete vrf name vrf-pink protocols ospf parameters +- delete vrf name vrf-pink protocols ospf neighbor +- delete vrf name vrf-pink protocols ospf redistribute +- set vrf name vrf-pink protocols ospf default-information originate metric 20 +- set vrf name vrf-pink protocols ospf default-information originate metric-type 1 +``` +--- +```` diff --git a/README.md b/README.md index df9d916e2..c453038b8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Name | Description [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 diff --git a/changelogs/fragments/t6838_vrf.yml b/changelogs/fragments/t6838_vrf.yml new file mode 100644 index 000000000..9483e4667 --- /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 000000000..9976ac9c2 --- /dev/null +++ b/docs/vyos.vyos.vyos_vrf_module.rst @@ -0,0 +1,5140 @@ +.. _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
+
+
+ protocols + +
+ dictionary +
+
+ +
Protocol configuration
+
+
+ bgp + +
+ dictionary +
+
+ +
BGP configuration
+
+
+ as_number + +
+ integer +
+
+ +
AS number.
+
+
+ bgp_params + +
+ dictionary +
+
+ +
BGP parameters
+
+
+ always_compare_med + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Always compare MEDs from different neighbors
+
+
+ bestpath + +
+ dictionary +
+
+ +
Default bestpath selection mechanism
+
+
+ as_path + +
+ string +
+
+
    Choices: +
  • confed
  • +
  • ignore
  • +
+
+
AS-path attribute comparison parameters
+
+
+ compare_routerid + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Compare the router-id for identical EBGP paths
+
+
+ med + +
+ string +
+
+
    Choices: +
  • confed
  • +
  • missing-as-worst
  • +
+
+
MED attribute comparison parameters
+
+
+ cluster_id + +
+ string +
+
+ +
Route-reflector cluster-id
+
+
+ confederation + +
+ list + / elements=dictionary +
+
+ +
AS confederation parameters
+
+
+ identifier + +
+ integer +
+
+ +
Confederation AS identifier
+
+
+ peers + +
+ integer +
+
+ +
Peer ASs in the BGP confederation
+
+
+ dampening + +
+ dictionary +
+
+ +
Enable route-flap dampening
+
+
+ half_life + +
+ integer +
+
+ +
Half-life penalty in seconds
+
+
+ max_suppress_time + +
+ integer +
+
+ +
Maximum duration to suppress a stable route
+
+
+ re_use + +
+ integer +
+
+ +
Time to start reusing a route
+
+
+ start_suppress_time + +
+ integer +
+
+ +
When to start suppressing a route
+
+
+ default + +
+ dictionary +
+
+ +
BGP defaults
+
+
+ local_pref + +
+ integer +
+
+ +
Default local preference
+
+
+ no_ipv4_unicast + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Deactivate IPv4 unicast for a peer by default + Deprecated: Unavailable after 1.4
+
+
+ deterministic_med + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Compare MEDs between different peers in the same AS
+
+
+ disable_network_import_check + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disable IGP route check for network statements
+
+
+ distance + +
+ list + / elements=dictionary +
+
+ +
Administrative distances for BGP routes
+
+
+ prefix + +
+ integer +
+
+ +
Administrative distance for a specific BGP prefix
+
+
+ type + +
+ string +
+
+
    Choices: +
  • external
  • +
  • internal
  • +
  • local
  • +
+
+
Type of route
+
+
+ value + +
+ integer +
+
+ +
distance
+
+
+ enforce_first_as + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Require first AS in the path to match peer's AS
+
+
+ graceful_restart + +
+ integer +
+
+ +
Maximum time to hold onto restarting peer's stale paths
+
+
+ log_neighbor_changes + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Log neighbor up/down changes and reset reason
+
+
+ no_client_to_client_reflection + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disable client to client route reflection
+
+
+ no_fast_external_failover + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disable immediate session reset if peer's connected link goes down
+
+
+ router_id + +
+ string +
+
+ +
BGP router-id
+
+
+ scan_time + +
+ integer +
+
+ +
BGP route scanner interval
+
+
+ neighbor + +
+ list + / elements=dictionary +
+
+ +
BGP neighbor
+
+
+ address + +
+ string +
+
+ +
BGP neighbor address (v4/v6).
+
+
+ advertisement_interval + +
+ integer +
+
+ +
Minimum interval for sending routing updates.
+
+
+ capability + +
+ dictionary +
+
+ +
Advertise capabilities to this neighbor.
+
+
+ dynamic + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Advertise dynamic capability to this neighbor.
+
+
+ extended_nexthop + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Advertise extended nexthop capability to this neighbor.
+
+
+ default_originate + +
+ string +
+
+ +
Send default route to this neighbor
+
+
+ description + +
+ string +
+
+ +
Description of the neighbor
+
+
+ disable_capability_negotiation + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disbale capability negotiation with the neighbor
+
+
+ disable_connected_check + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disable check to see if EBGP peer's address is a connected route.
+
+
+ disable_send_community + +
+ string +
+
+
    Choices: +
  • extended
  • +
  • standard
  • +
+
+
Disable sending community attributes to this neighbor.
+
+
+ ebgp_multihop + +
+ integer +
+
+ +
Allow this EBGP neighbor to not be on a directly connected network. Specify the number hops.
+
+
+ local_as + +
+ integer +
+
+ +
local as number not to be prepended to updates from EBGP peers
+
+
+ override_capability + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Ignore capability negotiation with specified neighbor.
+
+
+ passive + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Do not initiate a session with this neighbor
+
+
+ password + +
+ string +
+
+ +
BGP MD5 password
+
+
+ peer_group + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
True if all the configs under this neighbor key is for peer group template.
+
+
+ peer_group_name + +
+ string +
+
+ +
IPv4 peer group for this peer
+
+
+ port + +
+ integer +
+
+ +
Neighbor's BGP port
+
+
+ remote_as + +
+ integer +
+
+ +
Neighbor BGP AS number
+
+
+ shutdown + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Administratively shut down neighbor
+
+
+ solo + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Do not send back prefixes learned from the neighbor
+
+
+ strict_capability_match + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable strict capability negotiation
+
+
+ timers + +
+ dictionary +
+
+ +
Neighbor timers
+
+
+ connect + +
+ integer +
+
+ +
BGP connect timer for this neighbor.
+
+
+ holdtime + +
+ integer +
+
+ +
BGP hold timer for this neighbor
+
+
+ keepalive + +
+ integer +
+
+ +
BGP keepalive interval for this neighbor
+
+
+ ttl_security + +
+ integer +
+
+ +
Number of the maximum number of hops to the BGP peer
+
+
+ update_source + +
+ string +
+
+ +
Source IP of routing updates
+
+
+ timers + +
+ dictionary +
+
+ +
BGP protocol timers
+
+
+ holdtime + +
+ integer +
+
+ +
Hold time interval
+
+
+ keepalive + +
+ integer +
+
+ +
Keepalive interval
+
+
+ ospf + +
+ dictionary +
+
+ +
OSPFv2 configuration
+
+
+ areas + +
+ list + / elements=dictionary +
+
+ +
OSPFv2 area.
+
+
+ area_id + +
+ string +
+
+ +
OSPFv2 area identity.
+
+
+ area_type + +
+ dictionary +
+
+ +
Area type.
+
+
+ normal + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Normal OSPFv2 area.
+
+
+ nssa + +
+ dictionary +
+
+ +
NSSA OSPFv2 area.
+
+
+ default_cost + +
+ integer +
+
+ +
Summary-default cost of NSSA area.
+
+
+ no_summary + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Do not inject inter-area routes into stub.
+
+
+ set + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enabling NSSA.
+
+
+ translate + +
+ string +
+
+
    Choices: +
  • always
  • +
  • candidate
  • +
  • never
  • +
+
+
NSSA-ABR.
+
+
+ stub + +
+ dictionary +
+
+ +
Stub OSPFv2 area.
+
+
+ default_cost + +
+ integer +
+
+ +
Summary-default cost of stub area.
+
+
+ no_summary + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Do not inject inter-area routes into stub.
+
+
+ set + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enabling stub.
+
+
+ authentication + +
+ string +
+
+
    Choices: +
  • plaintext-password
  • +
  • md5
  • +
+
+
OSPFv2 area authentication type.
+
+
+ network + +
+ list + / elements=dictionary +
+
+ +
OSPFv2 network.
+
+
+ address + +
+ string + / required +
+
+ +
OSPFv2 IPv4 network address.
+
+
+ range + +
+ list + / elements=dictionary +
+
+ +
Summarize routes matching prefix (border routers only).
+
+
+ address + +
+ string +
+
+ +
border router IPv4 address.
+
+
+ cost + +
+ integer +
+
+ +
Metric for this range.
+
+
+ not_advertise + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Don't advertise this range.
+
+
+ substitute + +
+ string +
+
+ +
Announce area range (IPv4 address) as another prefix.
+
+
+ shortcut + +
+ string +
+
+
    Choices: +
  • default
  • +
  • disable
  • +
  • enable
  • +
+
+
Area's shortcut mode.
+
+
+ virtual_link + +
+ list + / elements=dictionary +
+
+ +
Virtual link address.
+
+
+ address + +
+ string +
+
+ +
virtual link address.
+
+
+ authentication + +
+ dictionary +
+
+ +
OSPFv2 area authentication type.
+
+
+ md5 + +
+ list + / elements=dictionary +
+
+ +
MD5 key id based authentication.
+
+
+ key_id + +
+ integer +
+
+ +
MD5 key id.
+
+
+ md5_key + +
+ string +
+
+ +
MD5 key.
+
+
+ plaintext_password + +
+ string +
+
+ +
Plain text password.
+
+
+ dead_interval + +
+ integer +
+
+ +
Interval after which a neighbor is declared dead.
+
+
+ hello_interval + +
+ integer +
+
+ +
Interval between hello packets.
+
+
+ retransmit_interval + +
+ integer +
+
+ +
Interval between retransmitting lost link state advertisements.
+
+
+ transmit_delay + +
+ integer +
+
+ +
Link state transmit delay.
+
+
+ auto_cost + +
+ dictionary +
+
+ +
Calculate OSPFv2 interface cost according to bandwidth.
+
+
+ reference_bandwidth + +
+ integer +
+
+ +
Reference bandwidth cost in Mbits/sec.
+
+
+ default_information + +
+ dictionary +
+
+ +
Control distribution of default information.
+
+
+ originate + +
+ dictionary +
+
+ +
Distribute a default route.
+
+
+ always + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Always advertise default route.
+
+
+ metric + +
+ integer +
+
+ +
OSPFv2 default metric.
+
+
+ metric_type + +
+ integer +
+
+ +
OSPFv2 Metric types for default routes.
+
+
+ route_map + +
+ string +
+
+ +
Route map references.
+
+
+ default_metric + +
+ integer +
+
+ +
Metric of redistributed routes
+
+
+ distance + +
+ dictionary +
+
+ +
Administrative distance.
+
+
+ global + +
+ integer +
+
+ +
Global OSPFv2 administrative distance.
+
+
+ ospf + +
+ dictionary +
+
+ +
OSPFv2 administrative distance.
+
+
+ external + +
+ integer +
+
+ +
Distance for external routes.
+
+
+ inter_area + +
+ integer +
+
+ +
Distance for inter-area routes.
+
+
+ intra_area + +
+ integer +
+
+ +
Distance for intra-area routes.
+
+
+ log_adjacency_changes + +
+ string +
+
+
    Choices: +
  • detail
  • +
+
+
Log changes in adjacency state.
+
+
+ max_metric + +
+ dictionary +
+
+ +
OSPFv2 maximum/infinite-distance metric.
+
+
+ router_lsa + +
+ dictionary +
+
+ +
Advertise own Router-LSA with infinite distance (stub router).
+
+
+ administrative + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Administratively apply, for an indefinite period.
+
+
+ on_shutdown + +
+ integer +
+
+ +
Time to advertise self as stub-router.
+
+
+ on_startup + +
+ integer +
+
+ +
Time to advertise self as stub-router
+
+
+ mpls_te + +
+ dictionary +
+
+ +
MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters.
+
+
+ enabled + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable MPLS-TE functionality.
+
+
+ router_address + +
+ string +
+
+ +
Stable IP address of the advertising router.
+
+
+ neighbor + +
+ list + / elements=dictionary +
+
+ +
Neighbor IP address.
+
+
+ neighbor_id + +
+ string +
+
+ +
Identity (number/IP address) of neighbor.
+
+
+ poll_interval + +
+ integer +
+
+ +
Seconds between dead neighbor polling interval.
+
+
+ priority + +
+ integer +
+
+ +
Neighbor priority.
+
+
+ parameters + +
+ dictionary +
+
+ +
OSPFv2 specific parameters.
+
+
+ abr_type + +
+ string +
+
+
    Choices: +
  • cisco
  • +
  • ibm
  • +
  • shortcut
  • +
  • standard
  • +
+
+
OSPFv2 ABR Type.
+
+
+ opaque_lsa + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable the Opaque-LSA capability (rfc2370).
+
+
+ rfc1583_compatibility + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Enable rfc1583 criteria for handling AS external routes.
+
+
+ router_id + +
+ string +
+
+ +
Override the default router identifier.
+
+
+ passive_interface + +
+ list + / elements=string +
+
+ +
Suppress routing updates on an interface.
+
+
+ passive_interface_exclude + +
+ list + / elements=string +
+
+ +
Interface to exclude when using passive-interface default.
+
+
+ redistribute + +
+ list + / elements=dictionary +
+
+ +
Redistribute information from another routing protocol.
+
+
+ metric + +
+ integer +
+
+ +
Metric for redistribution routes.
+
+
+ metric_type + +
+ integer +
+
+ +
OSPFv2 Metric types.
+
+
+ route_map + +
+ string +
+
+ +
Route map references.
+
+
+ route_type + +
+ string +
+
+
    Choices: +
  • bgp
  • +
  • connected
  • +
  • kernel
  • +
  • rip
  • +
  • static
  • +
+
+
Route type to redistribute.
+
+
+ route_map + +
+ list + / elements=string +
+
+ +
Filter routes installed in local route map.
+
+
+ timers + +
+ dictionary +
+
+ +
Adjust routing timers.
+
+
+ refresh + +
+ dictionary +
+
+ +
Adjust refresh parameters.
+
+
+ timers + +
+ integer +
+
+ +
refresh timer.
+
+
+ throttle + +
+ dictionary +
+
+ +
Throttling adaptive timers.
+
+
+ spf + +
+ dictionary +
+
+ +
OSPFv2 SPF timers.
+
+
+ delay + +
+ integer +
+
+ +
Delay (msec) from first change received till SPF calculation.
+
+
+ initial_holdtime + +
+ integer +
+
+ +
Initial hold time(msec) between consecutive SPF calculations.
+
+
+ max_holdtime + +
+ integer +
+
+ +
maximum hold time (sec).
+
+
+ ospfv3 + +
+ dictionary +
+
+ +
OSPFv3 configuration
+
+
+ areas + +
+ list + / elements=dictionary +
+
+ +
OSPFv3 area.
+
+
+ area_id + +
+ string +
+
+ +
OSPFv3 Area name/identity.
+
+
+ export_list + +
+ string +
+
+ +
Name of export-list.
+
+
+ import_list + +
+ string +
+
+ +
Name of import-list.
+
+
+ interface + +
+ list + / elements=dictionary +
+
+ +
Enable OSPVv3 on an interface for this area.
+

aliases: interfaces
+
+
+ name + +
+ string +
+
+ +
Interface name.
+
+
+ range + +
+ list + / elements=dictionary +
+
+ +
Summarize routes matching prefix (border routers only).
+
+
+ address + +
+ string +
+
+ +
border router IPv4 address.
+
+
+ advertise + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Advertise this range.
+
+
+ not_advertise + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Don't advertise this range.
+
+
+ parameters + +
+ dictionary +
+
+ +
OSPFv3 specific parameters.
+
+
+ router_id + +
+ string +
+
+ +
Override the default router identifier.
+
+
+ redistribute + +
+ list + / elements=dictionary +
+
+ +
Redistribute information from another routing protocol.
+
+
+ route_map + +
+ string +
+
+ +
Route map references.
+
+
+ route_type + +
+ string +
+
+
    Choices: +
  • bgp
  • +
  • connected
  • +
  • kernel
  • +
  • ripng
  • +
  • static
  • +
+
+
Route type to redistribute.
+
+
+ static + +
+ list + / elements=dictionary +
+
+ +
Static routes configuration
+
+
+ address_families + +
+ list + / elements=dictionary +
+
+ +
A dictionary specifying the address family to which the static route(s) belong.
+
+
+ afi + +
+ string + / required +
+
+
    Choices: +
  • ipv4
  • +
  • ipv6
  • +
+
+
Specifies the type of route.
+
+
+ routes + +
+ list + / elements=dictionary +
+
+ +
A dictionary that specify the static route configurations.
+
+
+ blackhole_config + +
+ dictionary +
+
+ +
Configured to silently discard packets.
+
+
+ distance + +
+ integer +
+
+ +
Distance for the route.
+
+
+ type + +
+ string +
+
+ +
This is to configure only blackhole.
+
+
+ dest + +
+ string + / required +
+
+ +
An IPv4/v6 address in CIDR notation that specifies the destination network for the static route.
+
+
+ next_hops + +
+ list + / elements=dictionary +
+
+ +
Next hops to the specified destination.
+
+
+ admin_distance + +
+ integer +
+
+ +
Distance value for the route.
+
+
+ enabled + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Disable IPv4/v6 next-hop static route.
+
+
+ forward_router_address + +
+ string +
+
+ +
The IP address of the next hop that can be used to reach the destination network.
+
+
+ interface + +
+ string +
+
+ +
Name of the outgoing interface.
+
+
+ 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.4.2 and 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 588996745..36579e05c 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -64,3 +64,5 @@ plugin_routing: 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 000000000..331a791fc --- /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 000000000..e69de29bb 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 000000000..46d956e81 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# 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 +""" + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp_global.bgp_global import ( + Bgp_globalArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import ( + Ospfv2Args, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) + + +class VrfArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_vrf module""" + + bgp_argument_spec = Bgp_globalArgs.argument_spec["config"] + static_routes_argument_spec = Static_routesArgs.argument_spec["config"] + ospfv2_argument_spec = Ospfv2Args.argument_spec["config"] + ospfv3_argument_spec = Ospfv3Args.argument_spec["config"] + + 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", + ], + }, + }, + }, + }, + }, + "protocols": { + # "type": "list", # sanity + # "elements": "dict", + "type": "dict", + "options": { + "bgp": bgp_argument_spec, + "ospf": ospfv2_argument_spec, + "ospfv3": ospfv3_argument_spec, + "static": static_routes_argument_spec, + }, + }, + }, + }, + }, + }, + "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 000000000..e69de29bb diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.old b/plugins/module_utils/network/vyos/config/vrf/vrf.old new file mode 100644 index 000000000..b9f56cc61 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.old @@ -0,0 +1,313 @@ +# +# -*- 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. +""" + +import importlib + +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.config.bgp_global.bgp_global import ( + Bgp_global, +) +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", + ] + self.bgp = Bgp_global(module) + + 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) + + if "protocols" in entry: + for protocol_name in entry["protocols"]: + protocol_module = self._load_protocol_module(protocol_name) + w_p_dict = entry["protocols"][protocol_name] + h_p_dict = next( + ( + v.get("protocols", {}).get(protocol_name) + for v in have + if v.get("name") == wname + ), + {}, + ) + if protocol_name == "bgp": + protocol_module._validate_template() + protocol_module.want = w_p_dict + protocol_module.have = h_p_dict + protocol_module.generate_commands() + elif protocol_name in [ + # "ospf", + # "ospfv3", + "static", + ]: + self._module.fail_json(msg=str(protocol_module)) + protocol_module._module.params["config"] = w_p_dict + protocol_module.state = self.state + self._module.fail_json(msg=str(protocol_module.set_config(h_p_dict))) + + protocol_module.commands = protocol_module.set_config(h_p_dict) + self.commands.extend( + [ + cmd.replace("protocols", "vrf name " + wname + " protocols", 1) + for cmd in protocol_module.commands + ], + ) + protocol_module = None # Clear the module to free resources + + 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}, + ) + + def _load_protocol_module(self, protocol_name): + if protocol_name == "bgp": + from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import ( + Bgp_global, + ) + + return Bgp_global(self._module) + elif protocol_name == "ospf": + from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( + Ospfv2, + ) + + return Ospfv2(self._module) + elif protocol_name == "ospfv3": + from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( + Ospfv3, + ) + + return Ospfv3(self._module) + elif protocol_name == "static": + from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( + Static_routes, + ) + + return Static_routes(self._module) + else: + self._module.fail_json(msg="The protocol is not supported") 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 000000000..068035515 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py @@ -0,0 +1,331 @@ +# +# -*- 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_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import ( + Bgp_global, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( + Ospfv2, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( + Ospfv3, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( + Static_routes, +) +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 w.items(): + 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 w.items(): + 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 wantd.items(): + 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) + + if "protocols" in entry: + for protocol_name in entry["protocols"]: + + w_p_dict = entry["protocols"][protocol_name] + + h_p_dict = next( + ( + v.get("protocols", {}).get(protocol_name, {}) + for v in have + if v.get("name") == wname + ), + {}, + ) + if protocol_name == "bgp": + bgp_module = Bgp_global(self._module) + bgp_module._validate_template() + bgp_module.want = w_p_dict + bgp_module.have = h_p_dict + bgp_module.generate_commands() + protocol_commands = bgp_module.commands + elif protocol_name == "ospf": + ospfv2_module = Ospfv2(self._module) + ospfv2_module._module.params["config"] = w_p_dict + ospfv2_module.state = self.state + protocol_commands = ospfv2_module.set_config(h_p_dict) + elif protocol_name == "ospfv3": + ospfv3_module = Ospfv3(self._module) + ospfv3_module._module.params["config"] = w_p_dict + ospfv3_module.state = self.state + protocol_commands = ospfv3_module.set_config(h_p_dict) + elif protocol_name == "static": + static_routes_module = Static_routes(self._module) + static_routes_module._module.params["config"] = w_p_dict + static_routes_module.state = self.state + # self._module.fail_json(msg=str(h_p_dict)) + # self._module.fail_json(msg="wafi: " + str(w_p_dict) + "**** hafi: " + str(h_p_dict)) + protocol_commands = static_routes_module.set_config(h_p_dict) + else: + self._module.fail_json( + msg="The protocol {} is not supported".format(protocol_name), + ) + self.commands.extend( + [ + cmd.replace("protocols", "vrf name " + wname + " protocols", 1) + for cmd in protocol_commands + ], + ) + + 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}, + ) + + # def _load_protocol_module(self, protocol_name): + # if protocol_name == "bgp": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import ( + # Bgp_global, + # ) + + # return Bgp_global(self._module) + # elif protocol_name == "ospf": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( + # Ospfv2, + # ) + + # return Ospfv2(self._module) + # elif protocol_name == "ospfv3": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( + # Ospfv3, + # ) + + # return Ospfv3(self._module) + # elif protocol_name == "static": + # self._module.fail_json(msg="here!") + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( + # Static_routes, + # ) + + # return Static_routes(self._module) + # else: + # self._module.fail_json(msg="The protocol is not supported") diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 74bbda747..24c444dde 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -80,6 +80,7 @@ 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) @@ -104,6 +105,7 @@ ntp_global=Ntp_globalFacts, snmp_server=Snmp_serverFacts, hostname=HostnameFacts, + vrf=VrfFacts, ) 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 000000000..e69de29bb 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 000000000..285e588f0 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py @@ -0,0 +1,244 @@ +# -*- 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.facts.bgp_global.bgp_global import ( + Bgp_globalFacts, +) +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.static_routes.static_routes import ( + Static_routesFacts, +) +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 and "protocols" in resource: + + protocol_lines = [] + for line in resource.strip().split("\n"): + if "protocols" in line: + idx = line.index("protocols") + protocol_lines.append("set " + line[idx:]) + objs["protocols"] = self._parse_protocols("\n".join(protocol_lines)) + + 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 not resources: + params["config"].pop("bind_to_all", None) + + if params.get("config"): + facts["vrf"] = params["config"] + ansible_facts["ansible_network_resources"].update(facts) + # self._module.fail_json(msg=ansible_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 + + def _parse_protocols(self, protocols): + """Parse protocols and return a dictionary""" + + protocol_chunks = {} + parsed_protocols = {} + + for line in protocols.split("\n"): + parts = line.split() + if len(parts) > 2 and parts[0] == "set" and parts[1] == "protocols": + protocol = parts[2] + protocol_chunks.setdefault(protocol, []).append(line) + + protocol_strings = {proto: "\n".join(lines) for proto, lines in protocol_chunks.items()} + + for protocol_name, protocol_string in protocol_strings.items(): + # protocol_module = self._load_protocol_module(protocol_name) + protocol_dict = {} + # protocol_dict = protocol_module.populate_facts( + # connection=self._module._connection, + # ansible_facts={"ansible_network_resources": {}}, + # data=protocol_string, + # ) + # parsed_protocols[protocol_name] = list( + # protocol_dict.get("ansible_network_resources").values(), + # )[0] + + if protocol_name == "bgp": + bgp_module = Bgp_globalFacts(self._module) + protocol_dict = bgp_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "ospf": + ospf_module = Ospfv2Facts(self._module) + protocol_dict = ospf_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "ospfv3": + ospfv3_module = Ospfv3Facts(self._module) + protocol_dict = ospfv3_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "static": + static_routes_module = Static_routesFacts(self._module) + # self._module.fail_json(msg=protocol_string) + protocol_dict = static_routes_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + # parsed_protocols[protocol_name] = [] + else: + self._module.fail_json(msg="The protocol is not supported" + protocol_name) + + # self._module.fail_json(msg=parsed_protocols) + return parsed_protocols 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 000000000..5171404e9 --- /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 }}", + }, + }, + { + "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 6957f6cb3..78445c024 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -285,3 +285,25 @@ def in_target_not_none(h, key): :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 000000000..e4c202cb3 --- /dev/null +++ b/plugins/modules/vyos_vrf.py @@ -0,0 +1,1598 @@ +#!/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.4.2 and 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 + protocols: + # type: list # sanity + # elements: dict + type: dict + description: Protocol configuration + suboptions: + bgp: + type: dict + description: BGP configuration + suboptions: + as_number: + description: + - AS number. + type: int + #maximum_paths: --> moved to address-family before 1.3 + neighbor: + description: BGP neighbor + type: list + elements: dict + suboptions: + address: + description: + - BGP neighbor address (v4/v6). + type: str + advertisement_interval: + description: + - Minimum interval for sending routing updates. + type: int + capability: + description: + - Advertise capabilities to this neighbor. + type: dict + suboptions: + dynamic: + description: + - Advertise dynamic capability to this neighbor. + type: bool + extended_nexthop: + description: + - Advertise extended nexthop capability to this neighbor. + type: bool + default_originate: + description: + - Send default route to this neighbor + type: str + description: + description: + - Description of the neighbor + type: str + disable_capability_negotiation: + description: + - Disbale capability negotiation with the neighbor + type: bool + disable_connected_check: + description: + - Disable check to see if EBGP peer's address is a connected route. + type: bool + disable_send_community: + description: + - Disable sending community attributes to this neighbor. + type: str + choices: ['extended', 'standard'] + ebgp_multihop: + description: + - Allow this EBGP neighbor to not be on a directly connected network. Specify + the number hops. + type: int + local_as: + description: local as number not to be prepended to updates from EBGP peers + type: int + override_capability: + description: Ignore capability negotiation with specified neighbor. + type: bool + passive: + description: Do not initiate a session with this neighbor + type: bool + password: + description: BGP MD5 password + type: str + peer_group_name: + description: IPv4 peer group for this peer + type: str + peer_group: + description: True if all the configs under this neighbor key is for peer group template. + type: bool + port: + description: Neighbor's BGP port + type: int + remote_as: + description: Neighbor BGP AS number + type: int + shutdown: + description: Administratively shut down neighbor + type: bool + solo: # <-- added in 1.3 + description: Do not send back prefixes learned from the neighbor + type: bool + strict_capability_match: + description: Enable strict capability negotiation + type: bool + timers: + description: Neighbor timers + type: dict + suboptions: + connect: + description: BGP connect timer for this neighbor. + type: int + holdtime: + description: BGP hold timer for this neighbor + type: int + keepalive: + description: BGP keepalive interval for this neighbor + type: int + ttl_security: + description: Number of the maximum number of hops to the BGP peer + type: int + update_source: + description: Source IP of routing updates + type: str + timers: + description: BGP protocol timers + type: dict + suboptions: + keepalive: + description: Keepalive interval + type: int + holdtime: + description: Hold time interval + type: int + bgp_params: + description: BGP parameters + type: dict + suboptions: + always_compare_med: + description: Always compare MEDs from different neighbors + type: bool + bestpath: + description: Default bestpath selection mechanism + type: dict + suboptions: + as_path: + description: AS-path attribute comparison parameters + type: str + choices: ['confed', 'ignore'] + compare_routerid: + description: Compare the router-id for identical EBGP paths + type: bool + med: + description: MED attribute comparison parameters + type: str + choices: ['confed', 'missing-as-worst'] + cluster_id: + description: Route-reflector cluster-id + type: str + confederation: + description: AS confederation parameters + type: list + elements: dict + suboptions: + identifier: + description: Confederation AS identifier + type: int + peers: + description: Peer ASs in the BGP confederation + type: int + dampening: + description: Enable route-flap dampening + type: dict + suboptions: + half_life: + description: Half-life penalty in seconds + type: int + max_suppress_time: + description: Maximum duration to suppress a stable route + type: int + re_use: + description: Time to start reusing a route + type: int + start_suppress_time: + description: When to start suppressing a route + type: int + default: + description: BGP defaults + type: dict + suboptions: + local_pref: + description: Default local preference + type: int + no_ipv4_unicast: + description: | + Deactivate IPv4 unicast for a peer by default + Deprecated: Unavailable after 1.4 + type: bool + deterministic_med: + description: Compare MEDs between different peers in the same AS + type: bool + disable_network_import_check: + description: Disable IGP route check for network statements + type: bool + distance: + description: Administrative distances for BGP routes + type: list + elements: dict + suboptions: + type: + description: Type of route + type: str + choices: ['external', 'internal', 'local'] + value: + description: distance + type: int + prefix: + description: Administrative distance for a specific BGP prefix + type: int + enforce_first_as: + description: Require first AS in the path to match peer's AS + type: bool + graceful_restart: + description: Maximum time to hold onto restarting peer's stale paths + type: int + log_neighbor_changes: + description: Log neighbor up/down changes and reset reason + type: bool + no_client_to_client_reflection: + description: Disable client to client route reflection + type: bool + no_fast_external_failover: + description: Disable immediate session reset if peer's connected link goes down + type: bool + router_id: + description: BGP router-id + type: str + scan_time: + description: BGP route scanner interval + type: int + ospf: + type: dict + description: OSPFv2 configuration + suboptions: + areas: + description: OSPFv2 area. + type: list + elements: dict + suboptions: + area_id: + description: OSPFv2 area identity. + type: str + area_type: + description: Area type. + type: dict + suboptions: + normal: + description: Normal OSPFv2 area. + type: bool + nssa: + description: NSSA OSPFv2 area. + type: dict + suboptions: + set: + description: Enabling NSSA. + type: bool + default_cost: + description: Summary-default cost of NSSA area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + translate: + description: NSSA-ABR. + type: str + choices: [always, candidate, never] + stub: + description: Stub OSPFv2 area. + type: dict + suboptions: + set: + description: Enabling stub. + type: bool + default_cost: + description: Summary-default cost of stub area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + authentication: + description: OSPFv2 area authentication type. + type: str + choices: [plaintext-password, md5] + network: + description: OSPFv2 network. + type: list + elements: dict + suboptions: + address: + required: true + description: OSPFv2 IPv4 network address. + type: str + range: + description: Summarize routes matching prefix (border routers only). + type: list + elements: dict + suboptions: + address: + description: border router IPv4 address. + type: str + cost: + description: Metric for this range. + type: int + not_advertise: + description: Don't advertise this range. + type: bool + substitute: + description: Announce area range (IPv4 address) as another prefix. + type: str + shortcut: + description: Area's shortcut mode. + type: str + choices: [default, disable, enable] + virtual_link: + description: Virtual link address. + type: list + elements: dict + suboptions: + address: + description: virtual link address. + type: str + authentication: + description: OSPFv2 area authentication type. + type: dict + suboptions: + md5: + description: MD5 key id based authentication. + type: list + elements: dict + suboptions: + key_id: + description: MD5 key id. + type: int + md5_key: + description: MD5 key. + type: str + plaintext_password: + description: Plain text password. + type: str + dead_interval: + description: Interval after which a neighbor is declared dead. + type: int + hello_interval: + description: Interval between hello packets. + type: int + retransmit_interval: + description: Interval between retransmitting lost link state advertisements. + type: int + transmit_delay: + description: Link state transmit delay. + type: int + log_adjacency_changes: + description: Log changes in adjacency state. + type: str + choices: [detail] + max_metric: + description: OSPFv2 maximum/infinite-distance metric. + type: dict + suboptions: + router_lsa: + description: Advertise own Router-LSA with infinite distance (stub router). + type: dict + suboptions: + administrative: + description: Administratively apply, for an indefinite period. + type: bool + on_shutdown: + description: Time to advertise self as stub-router. + type: int + on_startup: + description: Time to advertise self as stub-router + type: int + auto_cost: + description: Calculate OSPFv2 interface cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: Reference bandwidth cost in Mbits/sec. + type: int + default_information: + description: Control distribution of default information. + type: dict + suboptions: + originate: + description: Distribute a default route. + type: dict + suboptions: + always: + description: Always advertise default route. + type: bool + metric: + description: OSPFv2 default metric. + type: int + metric_type: + description: OSPFv2 Metric types for default routes. + type: int + route_map: + description: Route map references. + type: str + default_metric: + description: Metric of redistributed routes + type: int + distance: + description: Administrative distance. + type: dict + suboptions: + global: + description: Global OSPFv2 administrative distance. + type: int + ospf: + description: OSPFv2 administrative distance. + type: dict + suboptions: + external: + description: Distance for external routes. + type: int + inter_area: + description: Distance for inter-area routes. + type: int + intra_area: + description: Distance for intra-area routes. + type: int + mpls_te: + description: MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters. + type: dict + suboptions: + enabled: + description: Enable MPLS-TE functionality. + type: bool + router_address: + description: Stable IP address of the advertising router. + type: str + neighbor: + description: Neighbor IP address. + type: list + elements: dict + suboptions: + neighbor_id: + description: Identity (number/IP address) of neighbor. + type: str + poll_interval: + description: Seconds between dead neighbor polling interval. + type: int + priority: + description: Neighbor priority. + type: int + parameters: + description: OSPFv2 specific parameters. + type: dict + suboptions: + abr_type: + description: OSPFv2 ABR Type. + type: str + choices: [cisco, ibm, shortcut, standard] + opaque_lsa: + description: Enable the Opaque-LSA capability (rfc2370). + type: bool + rfc1583_compatibility: + description: Enable rfc1583 criteria for handling AS external routes. + type: bool + router_id: + description: Override the default router identifier. + type: str + passive_interface: + description: Suppress routing updates on an interface. + type: list + elements: str + passive_interface_exclude: + description: Interface to exclude when using passive-interface default. + type: list + elements: str + redistribute: + description: Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + route_type: + description: Route type to redistribute. + type: str + choices: [bgp, connected, kernel, rip, static] + metric: + description: Metric for redistribution routes. + type: int + metric_type: + description: OSPFv2 Metric types. + type: int + route_map: + description: Route map references. + type: str + route_map: + description: Filter routes installed in local route map. + type: list + elements: str + timers: + description: Adjust routing timers. + type: dict + suboptions: + refresh: + description: Adjust refresh parameters. + type: dict + suboptions: + timers: + description: refresh timer. + type: int + throttle: + description: Throttling adaptive timers. + type: dict + suboptions: + spf: + description: OSPFv2 SPF timers. + type: dict + suboptions: + delay: + description: Delay (msec) from first change received till SPF + calculation. + type: int + initial_holdtime: + description: Initial hold time(msec) between consecutive SPF calculations. + type: int + max_holdtime: + description: maximum hold time (sec). + type: int + ospfv3: + type: dict + description: OSPFv3 configuration + suboptions: + areas: + description: OSPFv3 area. + type: list + elements: dict + suboptions: + area_id: + description: OSPFv3 Area name/identity. + type: str + export_list: + description: Name of export-list. + type: str + import_list: + description: Name of import-list. + type: str + interface: + description: Enable OSPVv3 on an interface for this area. + aliases: ['interfaces'] + type: list + elements: dict + suboptions: + name: + description: Interface name. + type: str + range: + description: Summarize routes matching prefix (border routers only). + type: list + elements: dict + suboptions: + address: + description: border router IPv4 address. + type: str + advertise: + description: Advertise this range. + type: bool + not_advertise: + description: Don't advertise this range. + type: bool + parameters: + description: OSPFv3 specific parameters. + type: dict + suboptions: + router_id: + description: Override the default router identifier. + type: str + redistribute: + description: Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + route_type: + description: Route type to redistribute. + type: str + choices: + - bgp + - connected + - kernel + - ripng + - static + route_map: + description: Route map references. + type: str + static: + type: list + description: Static routes configuration + elements: dict + suboptions: + address_families: + description: A dictionary specifying the address family to which the static + route(s) belong. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the type of route. + type: str + choices: + - ipv4 + - ipv6 + required: true + routes: + description: A dictionary that specify the static route configurations. + type: list + elements: dict + suboptions: + dest: + description: + - An IPv4/v6 address in CIDR notation that specifies the destination + network for the static route. + type: str + required: true + blackhole_config: + description: + - Configured to silently discard packets. + type: dict + suboptions: + type: + description: + - This is to configure only blackhole. + type: str + distance: + description: + - Distance for the route. + type: int + next_hops: + description: + - Next hops to the specified destination. + type: list + elements: dict + suboptions: + forward_router_address: + description: + - The IP address of the next hop that can be used to reach the + destination network. + type: str + enabled: + description: + - Disable IPv4/v6 next-hop static route. + type: bool + admin_distance: + description: + - Distance value for the route. + type: int + interface: + description: + - Name of the outgoing interface. + type: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the 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/pr408-diagram.png b/pr408-diagram.png new file mode 100644 index 000000000..029208e0d Binary files /dev/null and b/pr408-diagram.png differ 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 000000000..164afead2 --- /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 000000000..daccf7209 --- /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 000000000..e63785819 --- /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 000000000..ddbed78ef --- /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 000000000..2b083b793 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/_parsed.cfg @@ -0,0 +1,34 @@ +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 +set vrf name vrf-green protocols ospf area '4' +set vrf name vrf-green protocols ospf area 4 area-type stub default-cost 20 +set vrf name vrf-green protocols ospf area 4 network 192.0.2.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 cost 10 +set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 +set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 cost 12 +set vrf name vrf-green protocols ospfv3 area '2' +set vrf name vrf-green protocols ospfv3 area 2 export-list export1 +set vrf name vrf-green protocols ospfv3 area 2 import-list import1 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db10::/32 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db20::/32 +set vrf name vrf-green protocols ospfv3 area 2 range 2001:db30::/32 +set vrf name vrf-green protocols static route 192.0.2.0/24 +set vrf name vrf-green protocols static route 192.0.2.0/24 blackhole distance '10' +set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.1' +set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.2' +set vrf name vrf-green protocols static route6 2001:db8::/32 +set vrf name vrf-green protocols static route6 2001:db8::/32 blackhole distance '20' +set vrf name vrf-green protocols static route6 2001:db8::/32 next-hop '2001:db8::1'" 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 000000000..9e6253ee2 --- /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 000000000..d909adfdd --- /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 000000000..e69de29bb 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 000000000..b6a28d0a0 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/deleted.yaml @@ -0,0 +1,34 @@ +--- +- 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.changed == true + - result.commands|symmetric_difference(deleted.commands) == [] + + - name: Assert that the after dicts were correctly generated + assert: + that: + - result.after|symmetric_difference(deleted.after) == [] + + - 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 000000000..ba1dab0da --- /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 000000000..7ea7b2863 --- /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 000000000..0a93ae02b --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/merged.yaml @@ -0,0 +1,54 @@ +--- +- 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: result + vyos.vyos.vyos_vrf: &id001 + config: + bind_to_all: true + instances: + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.5.1 + remote_as: 65012 + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrf + + - assert: + that: + - 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 000000000..81cd97350 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/overridden.yaml @@ -0,0 +1,50 @@ +--- +- 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 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.2.1 + remote_as: 65002 + 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 000000000..7f6a4b3df --- /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.before) == [] + +- 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 000000000..171636541 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rendered.yaml @@ -0,0 +1,133 @@ +--- +- 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 + protocols: + bgp: + as_number: 65000 + neighbor: + - address: 192.0.2.1 + remote_as: 65002 + - address: "1.1.1.3" + passive: true + remote_as: 400 + ospf: + log_adjacency_changes: detail + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + mpls_te: + router_address: 192.0.11.12 + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: 192.0.11.12 + poll_interval: 10 + priority: 2 + redistribute: + - route_type: bgp + metric: 10 + metric_type: 2 + parameters: + router_id: 192.0.1.1 + rfc1583_compatibility: true + abr_type: cisco + areas: + - area_id: "2" + area_type: + normal: true + authentication: plaintext-password + shortcut: enable + - area_id: "3" + area_type: + nssa: + set: true + - area_id: "4" + area_type: + stub: + default_cost: 20 + network: + - address: 192.0.2.0/24 + range: + - address: 192.0.3.0/24 + cost: 10 + - address: 192.0.4.0/24 + cost: 12 + ospfv3: + areas: + - area_id: "2" + export_list: export1 + import_list: import1 + range: + - address: 2001:db10::/32 + - address: 2001:db20::/32 + - address: 2001:db30::/32 + - area_id: "3" + range: + - address: 2001:db40::/32 + parameters: + router_id: 192.0.2.10 + redistribute: + - route_type: bgp + static: + - address_families: + - afi: ipv4 + routes: + - dest: "192.0.2.0/24" + blackhole_config: + distance: 10 + next_hops: + - forward_router_address: "203.0.113.1" + - forward_router_address: "203.0.113.2" + - afi: ipv6 + routes: + - dest: "2001:db8::/32" + blackhole_config: + distance: 20 + next_hops: + - forward_router_address: "2001:db8::1" + - 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 000000000..68d3e4506 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/replaced.yaml @@ -0,0 +1,56 @@ +--- +- 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 + - name: "vrf-pink" + table_id: 111 + protocols: + ospf: + default_information: + originate: + always: true + metric: 20 + metric_type: 1 + 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 000000000..e8e9ea2c2 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/tests/cli/rtt.yaml @@ -0,0 +1,69 @@ +--- +- 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 + - name: "vrf-pink" + description: "pink-vrf" + table_id: 111 + vni: 1111 + protocols: + bgp: + as_number: 65005 + neighbor: + - address: 192.0.5.1 + remote_as: 65012 + 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 000000000..d6ac455e6 --- /dev/null +++ b/tests/integration/targets/vyos_vrf/vars/main.yaml @@ -0,0 +1,282 @@ +--- +merged: + before: + 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 + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + protocols: + ospf: + areas: + - area_id: "4" + area_type: + stub: + default_cost: 20 + network: + - address: 192.0.2.0/24 + range: + - address: 192.0.3.0/24 + cost: 10 + - address: 192.0.4.0/24 + cost: 12 + ospfv3: + areas: + - area_id: "2" + export_list: export1 + import_list: import1 + range: + - address: 2001:db10::/32 + - address: 2001:db20::/32 + - address: 2001:db30::/32 + static: + - address_families: + - afi: ipv4 + routes: + - dest: "192.0.2.0/24" + blackhole_config: + distance: 10 + next_hops: + - forward_router_address: "203.0.113.1" + - forward_router_address: "203.0.113.2" + - afi: ipv6 + routes: + - dest: "2001:db8::/32" + blackhole_config: + distance: 20 + next_hops: + - forward_router_address: "2001:db8::1" + commands: + - set vrf name vrf-green table 110 + - set vrf name vrf-green vni 1010 + - set vrf name vrf-green description green-vrf + - set vrf name vrf-pink protocols bgp system-as 65005 + - set vrf name vrf-pink protocols bgp neighbor 192.0.5.1 remote-as 65012 + 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 + - name: "vrf-green" + description: "green-vrf" + table_id: 110 + vni: 1010 + +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 + - delete vrf name vrf-pink vni 1111 + - delete vrf name vrf-pink description pink-vrf + - delete vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' + - delete vrf name vrf-pink protocols ospf auto-cost + - delete vrf name vrf-pink protocols ospf max-metric + - delete vrf name vrf-pink protocols ospf mpls-te + - delete vrf name vrf-pink protocols ospf area + - delete vrf name vrf-pink protocols ospf parameters + - delete vrf name vrf-pink protocols ospf neighbor + - delete vrf name vrf-pink protocols ospf redistribute + - set vrf name vrf-pink protocols ospf default-information originate metric 20 + - set vrf name vrf-pink protocols ospf default-information originate metric-type 1 + after: [] + +overridden: + commands: + - delete vrf name vrf-blue + - commit + - delete vrf name vrf-pink + - 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 + - set vrf name vrf-pink table 111 + - set vrf name vrf-pink vni 1111 + - set vrf name vrf-pink description pink-vrf + - set vrf name vrf-pink protocols bgp system-as 65005 + - set vrf name vrf-pink protocols bgp neighbor 192.0.2.1 remote-as 65002 + - 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-green protocols bgp system-as 65000 + - set vrf name vrf-green protocols bgp neighbor 192.0.2.1 remote-as 65002 + - set vrf name vrf-green protocols bgp neighbor 1.1.1.3 passive + - set vrf name vrf-green protocols bgp neighbor 1.1.1.3 remote-as 400 + - set vrf name vrf-green protocols ospf log-adjacency-changes 'detail' + - set vrf name vrf-green protocols ospf max-metric router-lsa administrative + - set vrf name vrf-green protocols ospf max-metric router-lsa on-shutdown 10 + - set vrf name vrf-green protocols ospf max-metric router-lsa on-startup 10 + - set vrf name vrf-green protocols ospf default-information originate always + - set vrf name vrf-green protocols ospf default-information originate metric 10 + - set vrf name vrf-green protocols ospf default-information originate metric-type 2 + - set vrf name vrf-green protocols ospf mpls-te router-address '192.0.11.12' + - set vrf name vrf-green protocols ospf auto-cost reference-bandwidth '2' + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set vrf name vrf-green protocols ospf neighbor 192.0.11.12 priority 2 + - set vrf name vrf-green protocols ospf redistribute bgp + - set vrf name vrf-green protocols ospf redistribute bgp metric 10 + - set vrf name vrf-green protocols ospf redistribute bgp metric-type 2 + - set vrf name vrf-green protocols ospf parameters router-id '192.0.1.1' + - set vrf name vrf-green protocols ospf parameters rfc1583-compatibility + - set vrf name vrf-green protocols ospf parameters abr-type 'cisco' + - set vrf name vrf-green protocols ospf area '2' + - set vrf name vrf-green protocols ospf area 2 area-type normal + - set vrf name vrf-green protocols ospf area 2 authentication plaintext-password + - set vrf name vrf-green protocols ospf area 2 shortcut enable + - set vrf name vrf-green protocols ospf area '3' + - set vrf name vrf-green protocols ospf area 3 area-type nssa + - set vrf name vrf-green protocols ospf area '4' + - set vrf name vrf-green protocols ospf area 4 area-type stub default-cost 20 + - set vrf name vrf-green protocols ospf area 4 network 192.0.2.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 + - set vrf name vrf-green protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set vrf name vrf-green protocols ospfv3 area '2' + - set vrf name vrf-green protocols ospfv3 area 2 export-list export1 + - set vrf name vrf-green protocols ospfv3 area 2 import-list import1 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db10::/32 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db20::/32 + - set vrf name vrf-green protocols ospfv3 area 2 range 2001:db30::/32 + - set vrf name vrf-green protocols ospfv3 area '3' + - set vrf name vrf-green protocols ospfv3 area 3 range 2001:db40::/32 + - set vrf name vrf-green protocols ospfv3 parameters router-id '192.0.2.10' + - set vrf name vrf-green protocols ospfv3 redistribute bgp + - set vrf name vrf-green protocols static route 192.0.2.0/24 + - set vrf name vrf-green protocols static route 192.0.2.0/24 blackhole distance '10' + - set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.1' + - set vrf name vrf-green protocols static route 192.0.2.0/24 next-hop '203.0.113.2' + - set vrf name vrf-green protocols static route6 2001:db8::/32 + - set vrf name vrf-green protocols static route6 2001:db8::/32 blackhole distance '20' + - set vrf name vrf-green protocols static route6 2001:db8::/32 next-hop '2001:db8::1' + - 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' + - set vrf name vrf-pink table 111 + - set vrf name vrf-pink vni 1111 + - set vrf name vrf-pink description pink-vrf + - set vrf name vrf-pink protocols bgp system-as 65000 + - set vrf name vrf-pink protocols bgp neighbor 192.0.2.1 remote-as 65002 + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 passive + - set vrf name vrf-pink protocols bgp neighbor 1.1.1.3 remote-as 400 + - set vrf name vrf-pink protocols ospf log-adjacency-changes 'detail' + - set vrf name vrf-pink protocols ospf max-metric router-lsa administrative + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-shutdown 10 + - set vrf name vrf-pink protocols ospf max-metric router-lsa on-startup 10 + - set vrf name vrf-pink protocols ospf default-information originate always + - set vrf name vrf-pink protocols ospf default-information originate metric 10 + - set vrf name vrf-pink protocols ospf default-information originate metric-type 2 + - set vrf name vrf-pink protocols ospf mpls-te router-address '192.0.11.12' + - set vrf name vrf-pink protocols ospf auto-cost reference-bandwidth '2' + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set vrf name vrf-pink protocols ospf neighbor 192.0.11.12 priority 2 + - set vrf name vrf-pink protocols ospf redistribute bgp + - set vrf name vrf-pink protocols ospf redistribute bgp metric 10 + - set vrf name vrf-pink protocols ospf redistribute bgp metric-type 2 + - set vrf name vrf-pink protocols ospf parameters router-id '192.0.1.1' + - set vrf name vrf-pink protocols ospf parameters rfc1583-compatibility + - set vrf name vrf-pink protocols ospf parameters abr-type 'cisco' + - set vrf name vrf-pink protocols ospf area '2' + - set vrf name vrf-pink protocols ospf area 2 area-type normal + - set vrf name vrf-pink protocols ospf area 2 authentication plaintext-password + - set vrf name vrf-pink protocols ospf area 2 shortcut enable + - set vrf name vrf-pink protocols ospf area '3' + - set vrf name vrf-pink protocols ospf area 3 area-type nssa + - set vrf name vrf-pink protocols ospf area '4' + - set vrf name vrf-pink protocols ospf area 4 area-type stub default-cost 20 + - set vrf name vrf-pink protocols ospf area 4 network 192.0.2.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 + - set vrf name vrf-pink protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set vrf name vrf-pink protocols ospfv3 area '2' + - set vrf name vrf-pink protocols ospfv3 area 2 export-list export1 + - set vrf name vrf-pink protocols ospfv3 area 2 import-list import1 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db10::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db20::/32 + - set vrf name vrf-pink protocols ospfv3 area 2 range 2001:db30::/32 + - set vrf name vrf-pink protocols ospfv3 area '3' + - set vrf name vrf-pink protocols ospfv3 area 3 range 2001:db40::/32 + - set vrf name vrf-pink protocols ospfv3 parameters router-id '192.0.2.10' + - set vrf name vrf-pink protocols ospfv3 redistribute bgp + - set vrf name vrf-pink protocols static route 192.0.2.0/24 + - set vrf name vrf-pink protocols static route 192.0.2.0/24 blackhole distance '10' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.1' + - set vrf name vrf-pink protocols static route 192.0.2.0/24 next-hop '203.0.113.2' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 + - set vrf name vrf-pink protocols static route6 2001:db8::/32 blackhole distance '20' + - set vrf name vrf-pink protocols static route6 2001:db8::/32 next-hop '2001:db8::1' 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 000000000..469de71b8 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vrf_config.cfg @@ -0,0 +1,16 @@ +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' +# set vrf name vrf-red protocols bgp system-as 65000 +# set vrf name vrf-red protocols bgp neighbor 192.0.2.1 remote-as 65002 +# set vrf name vrf-red protocols bgp neighbor 1.1.1.3 passive +# set vrf name vrf-red protocols bgp neighbor 1.1.1.3 remote-as 400 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 000000000..634598b4e --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vrf.py @@ -0,0 +1,523 @@ +# (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, + ), + ], + # protocols = dict( + # bgp=dict( + # as_number=65000, + # neighbor=[ + # dict( + # address="192.0.2.1", + # remote_as=65002, + # ), + # dict( + # address="1.1.1.3", + # remote_as=400, + # passive=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)