diff --git a/docs/vyos.vyos.vyos_ha_module.rst b/docs/vyos.vyos.vyos_ha_module.rst index a216e983..5e03661c 100644 --- a/docs/vyos.vyos.vyos_ha_module.rst +++ b/docs/vyos.vyos.vyos_ha_module.rst @@ -1,2522 +1,2523 @@ .. _vyos.vyos.vyos_ha_module: ***************** vyos.vyos.vyos_ha ***************** **Manage VRRP and load balancer configuration on VyOS** Version added: 1.0.0 .. contents:: :local: :depth: 1 Synopsis -------- - This module configures VRRP groups, global VRRP parameters, VRRP sync groups, and LVS-style virtual servers on VyOS 1.4+. - Supports creation, modification, deletion, replacement, rendering, and parsing of VRRP-related configuration. Parameters ---------- .. raw:: html
Parameter Choices/Defaults Comments
config
dictionary
Full VRRP and virtual server configuration.
disable
boolean
    Choices:
  • no ←
  • yes
Disable all VRRP and L4-LB configuration under this module.
virtual_servers
list / elements=dictionary
List of load balancer virtual server (LVS) definitions.
address
string
Virtual IP address for the server.
algorithm
string
Load balancing algorithm used for dispatching connections.
delay_loop
integer
Delay loop interval in seconds.
forward_method
string
    Choices:
  • direct
  • nat
Forwarding method used by LVS.
fwmark
integer
Firewall mark for LVS traffic classification.
name
string / required
Unique identifier for the virtual server.
persistence_timeout
integer
Client persistence timeout in seconds.
port
integer
TCP/UDP port provided by the virtual service.
protocol
string
    Choices:
  • tcp
  • udp
Transport protocol for the virtual server.
real_server
list / elements=dictionary
Backend real servers behind the virtual service.
address
string / required
Real server IP address.
connection_timeout
integer
Backend server connection timeout.
health_check_script
string
Path to health check script used for backend validation.
port
integer
Backend server port.
vrrp
dictionary
VRRP configuration including groups, global parameters, SNMP settings, and sync-groups.
global_parameters
dictionary
Global VRRP tuning parameters.
garp
dictionary
Gratuitous ARP related configuration.
interval
integer
GARP interval in seconds.
master_delay
integer
Delay before sending GARP as master.
master_refresh
integer
Refresh interval for master GARP announcements.
master_refresh_repeat
integer
Number of times to repeat refresh announcements.
master_repeat
integer
Number of GARP repeats when transitioning to master.
startup_delay
integer
Delay before VRRP starts after boot.
version
string
VRRP protocol version.
groups
list / elements=dictionary
VRRP instance configuration groups.
address
- string + list + / elements=string
-
Virtual router IP address.
+
Virtual router IP addresses.
advertise_interval
integer
VRRP advertisement interval.
authentication
dictionary
VRRP group authentication options.
password
string
Authentication password.
type
string
Authentication type.
description
string
Text description for the VRRP group.
disable
boolean
    Choices:
  • no ←
  • yes
Disable this VRRP group.
excluded_address
list / elements=string
IP address excluded from source checks.
garp
dictionary
GARP-specific settings for this group.
interval
integer
GARP interval.
master_delay
integer
GARP master delay.
master_refresh
integer
GARP master refresh interval.
master_refresh_repeat
integer
Repeated refresh sends.
master_repeat
integer
GARP repeat count.
health_check
dictionary
VRRP group health check options.
failure_count
integer
Allowed number of failed checks.
interval
integer
Health check interval.
ping
string
Host to ping for checks.
script
string
Script to execute for health checking.
hello_source_address
string
Source address for VRRP hello packets.
interface
string
Interface used by the VRRP group.
name
string / required
VRRP group name.
no_preempt
boolean
    Choices:
  • no ←
  • yes
Disable preemption.
peer_address
string
Peer VRRP router address.
preempt_delay
integer
Delay before taking master role.
priority
integer
VRRP priority (higher = preferred master).
rfc3768_compatibility
boolean
    Choices:
  • no ←
  • yes
Enable or disable RFC3768 compatibility mode.
track
dictionary
Track interface and VRRP behaviour.
exclude_vrrp_interface
boolean
    Choices:
  • no
  • yes
Exclude VRRP interface from tracking.
interface
list / elements=string
Interface to track.
transition_script
dictionary
Scripts executed during VRRP state transitions.
backup
string
Path to backup script.
fault
string
Path to fault script.
master
string
Path to master script.
stop
string
Path to stop script.
vrid
integer
VRRP Virtual Router ID.
snmp
string
    Choices:
  • enabled
  • disabled
Enable SNMP support for VRRP.
sync_groups
list / elements=dictionary
VRRP sync-groups for coordinated failover.
health_check
dictionary
Health check options for sync group.
failure_count
integer
Allowed number of failures.
interval
integer
Health check interval.
ping
string
Host to ping.
script
string
Script to run for health checking.
member
list / elements=string
List of VRRP groups participating in this sync group.
name
string / required
Sync-group name.
transition_script
dictionary
Transition scripts for sync group events.
backup
string
Backup state script.
fault
string
Fault state script.
master
string
Master state script.
stop
string
Stop state script.
running_config
string
Used only when state=parsed. Must contain the output of show configuration commands | grep high-availability.
state
string
    Choices:
  • deleted
  • merged ←
  • purged
  • replaced
  • gathered
  • rendered
  • parsed
  • overridden
Desired end state of the VRRP configuration.

Examples -------- .. code-block:: yaml # Using merged # Before state # vyos@vyos:~$ show configuration commands | match "set high-availability" # vyos@vyos:~$ - name: Merge provided configuration with device configuration vyos.vyos.vyos_ha: config: disable: true virtual_servers: - name: s1 address: 10.10.10.5 algorithm: round-robin real_server: - address: 10.10.50.2 port: 8443 - name: s2 address: 10.10.10.2 persistence_timeout: 30 port: 81 protocol: tcp - name: s3 address: 10.10.10.3 port: 88 protocol: udp vrrp: snmp: enabled global_parameters: startup_delay: 30 garp: master_repeat: 6 groups: - name: "g1" peer_address: 192.168.1.3 priority: 100 disable: false no_preempt: false vrid: 20 sync_groups: - name: "sg1" health_check: failure_count: 5 state: merged # After State # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s1 address '10.10.10.5' # set high-availability virtual-server s1 algorithm 'round-robin' # set high-availability virtual-server s1 real-server 10.10.50.2 port '8443' # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp global-parameters startup-delay '30' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ # # # Module Execution: # # "after": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.5", # "algorithm": "round-robin", # "name": "s1", # "real_server": [ # { # "address": "10.10.50.2", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # }, # "startup_delay": 30 # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # "before": { # "disable": false # }, # "changed": true, # "commands": [ # "set high-availability disable", # "set high-availability virtual-server s1 address 10.10.10.5", # "set high-availability virtual-server s1 algorithm round-robin", # "set high-availability virtual-server s1 real-server 10.10.50.2 port 8443", # "set high-availability virtual-server s2 address 10.10.10.2", # "set high-availability virtual-server s2 persistence-timeout 30", # "set high-availability virtual-server s2 port 81", # "set high-availability virtual-server s2 protocol tcp", # "set high-availability virtual-server s3 address 10.10.10.3", # "set high-availability virtual-server s3 port 88", # "set high-availability virtual-server s3 protocol udp", # "set high-availability vrrp global-parameters garp master-repeat 6", # "set high-availability vrrp global-parameters startup-delay 30", # "set high-availability vrrp group g1 peer-address 192.168.1.3", # "set high-availability vrrp group g1 priority 100", # "set high-availability vrrp group g1 vrid 20", # "set high-availability vrrp snmp", # "set high-availability vrrp sync-group sg1 health-check failure-count 5" # ], # Using replaced: # -------------- # Before state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s1 address '10.10.10.5' # set high-availability virtual-server s1 algorithm 'round-robin' # set high-availability virtual-server s1 real-server 10.10.50.2 port '8443' # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp global-parameters startup-delay '30' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ - name: Replace vyos.vyos.vyos_ha: config: disable: false virtual_servers: - name: s1 address: 10.10.10.3 algorithm: round-robin port: 8443 real_server: - address: 10.10.50.3 port: 8443 - name: s2 address: 10.10.10.2 persistence_timeout: 300 port: 81 protocol: tcp real_server: - address: 10.10.50.30 port: 8443 - name: s3 address: 10.10.10.3 port: 88 protocol: udp real_server: - address: 10.10.50.6 port: 8443 vrrp: snmp: enabled global_parameters: startup_delay: 30 garp: master_repeat: 6 groups: - name: "g1" peer_address: 192.168.1.13 priority: 100 disable: false no_preempt: true interface: eth1 address: 192.168.51.13 vrid: 20 sync_groups: - name: "sg1" health_check: failure_count: 3 state: replaced # After state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability virtual-server s1 address '10.10.10.3' # set high-availability virtual-server s1 algorithm 'round-robin' # set high-availability virtual-server s1 port '8443' # set high-availability virtual-server s1 real-server 10.10.50.2 port '8443' # set high-availability virtual-server s1 real-server 10.10.50.3 port '8443' # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '300' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s2 real-server 10.10.50.3 port '8443' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability virtual-server s3 real-server 10.10.50.6 port '8443' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp global-parameters startup-delay '30' # set high-availability vrrp group g1 address 192.168.51.13 # set high-availability vrrp group g1 interface 'eth1' # set high-availability vrrp group g1 no-preempt # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 peer-address '192.168.1.13' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '3' # vyos@vyos:~$ # # # Module Execution: # # "after": { # "disable": false, # "virtual_servers": [ # { # "address": "10.10.10.3", # "algorithm": "round-robin", # "name": "s1", # "port": 8443, # "real_server": [ # { # "address": "10.10.50.2", # "port": 8443 # }, # { # "address": "10.10.50.3", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 300, # "port": 81, # "protocol": "tcp", # "real_server": [ # { # "address": "10.10.50.3", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp", # "real_server": [ # { # "address": "10.10.50.6", # "port": 8443 # } # ] # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # }, # "startup_delay": 30 # }, # "groups": [ # { # "address": "192.168.51.13", # "disable": false, # "interface": "eth1", # "name": "g1", # "no_preempt": true, # "peer_address": "192.168.1.13", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 3 # }, # "name": "sg1" # } # ] # } # }, # "before": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.5", # "algorithm": "round-robin", # "name": "s1", # "real_server": [ # { # "address": "10.10.50.2", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # }, # "startup_delay": 30 # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # "changed": true, # "commands": [ # "delete high-availability disable", # "set high-availability virtual-server s1 address 10.10.10.3", # "set high-availability virtual-server s1 port 8443", # "set high-availability virtual-server s1 real-server 10.10.50.3 port 8443", # "set high-availability virtual-server s2 persistence-timeout 300", # "set high-availability virtual-server s2 real-server 10.10.50.3 port 8443", # "set high-availability virtual-server s3 real-server 10.10.50.6 port 8443", # "set high-availability vrrp group g1 address 192.168.51.13", # "set high-availability vrrp group g1 interface eth1", # "set high-availability vrrp group g1 no-preempt", # "set high-availability vrrp group g1 peer-address 192.168.1.13", # "set high-availability vrrp sync-group sg1 health-check failure-count 3" # ], # Using deleted: # ------------- # Before state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s1 address '10.10.10.5' # set high-availability virtual-server s1 algorithm 'round-robin' # set high-availability virtual-server s1 real-server 10.10.50.2 port '8443' # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp global-parameters startup-delay '30' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ - name: Delete configuration vyos.vyos.vyos_ha: config: disable: false vrrp: snmp: disabled global_parameters: startup_delay: 32 version: 3 virtual_servers: - name: 's1' address: '10.10.10.1' algorithm: 'round-robin' delay_loop: 60 forward_method: 'direct' persistence_timeout: 30 port: 443 protocol: 'tcp' real_server: - address: '10.10.10.1' connection_timeout: 61 port: 443 state: deleted # After state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ # # # Module Execution: # # "after": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # } # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # "before": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.5", # "algorithm": "round-robin", # "name": "s1", # "real_server": [ # { # "address": "10.10.50.2", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # }, # "startup_delay": 30 # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # "changed": true, # "commands": [ # "delete high-availability virtual-server s1", # "delete high-availability vrrp global-parameters startup-delay" # ], # Using purged: # Before state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ - name: Purge configuration vyos.vyos.vyos_ha: config: state: purged # After state: # vyos@vyos:~$ show configuration commands | match "set high-availability" # vyos@vyos:~$ # # Module Execution: # # "after": { # "disable": false # }, # "before": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # } # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # "changed": true, # "commands": [ # "delete high-availability" # ], # using gathered: # -------------- # Before state: # vyos@vyos:~$ # show configuration commands | match "set high-availability" # set high-availability disable # set high-availability virtual-server s1 address '10.10.10.5' # set high-availability virtual-server s1 algorithm 'round-robin' # set high-availability virtual-server s1 real-server 10.10.50.2 port '8443' # set high-availability virtual-server s2 address '10.10.10.2' # set high-availability virtual-server s2 persistence-timeout '30' # set high-availability virtual-server s2 port '81' # set high-availability virtual-server s2 protocol 'tcp' # set high-availability virtual-server s3 address '10.10.10.3' # set high-availability virtual-server s3 port '88' # set high-availability virtual-server s3 protocol 'udp' # set high-availability vrrp global-parameters garp master-repeat '6' # set high-availability vrrp global-parameters startup-delay '30' # set high-availability vrrp group g1 peer-address '192.168.1.3' # set high-availability vrrp group g1 priority '100' # set high-availability vrrp group g1 vrid '20' # set high-availability vrrp snmp # set high-availability vrrp sync-group sg1 health-check failure-count '5' # vyos@vyos:~$ - name: gather configs vyos.vyos.vyos_ha: state: gathered # Module Execution: # "changed": false, # "gathered": { # "disable": true, # "virtual_servers": [ # { # "address": "10.10.10.5", # "algorithm": "round-robin", # "name": "s1", # "real_server": [ # { # "address": "10.10.50.2", # "port": 8443 # } # ] # }, # { # "address": "10.10.10.2", # "name": "s2", # "persistence_timeout": 30, # "port": 81, # "protocol": "tcp" # }, # { # "address": "10.10.10.3", # "name": "s3", # "port": 88, # "protocol": "udp" # } # ], # "vrrp": { # "global_parameters": { # "garp": { # "master_repeat": 6 # }, # "startup_delay": 30 # }, # "groups": [ # { # "disable": false, # "name": "g1", # "no_preempt": false, # "peer_address": "192.168.1.3", # "priority": 100, # "rfc3768_compatibility": false, # "vrid": 20 # } # ], # "snmp": "enabled", # "sync_groups": [ # { # "health_check": { # "failure_count": 5 # }, # "name": "sg1" # } # ] # } # }, # # Using parsed: # ------------ # parsed.cfg # set high-availability vrrp group g1 interface eth2 # set high-availability vrrp group g1 address 1.1.1.1 # set high-availability vrrp group g1 disable # set high-availability vrrp group g1 no-preempt # set high-availability vrrp group g1 advertise-interval 10 # set high-availability vrrp group g1 peer-address 2.2.2.2 # set high-availability vrrp group g1 rfc3768-compatibility # set high-availability vrrp group g1 vrid 20 - name: parse configs vyos.vyos.vyos_ha: running_config: "{{ lookup('file', './parsed.cfg') }}" state: parsed # Module execution: # "parsed": { # "disable": false, # "vrrp": { # "groups": [ # { # "address": "1.1.1.1", # "advertise_interval": 10, # "disable": true, # "interface": "eth2", # "name": "g1", # "no_preempt": true, # "peer_address": "2.2.2.2", # "rfc3768_compatibility": true, # "vrid": 20 # } # ] # } # } # # Using rendered: # -------------- - name: Render vyos.vyos.vyos_ha: config: disable: true vrrp: snmp: enabled global_parameters: startup_delay: 32 version: 3 garp: interval: 30 master_delay: 11 master_refresh: 100 master_refresh_repeat: 200 master_repeat: 5 state: rendered # Module Execution: # "rendered": [ # "set high-availability disable", # "set high-availability vrrp global-parameters garp interval 30", # "set high-availability vrrp global-parameters garp master-delay 11", # "set high-availability vrrp global-parameters garp master-refresh 100", # "set high-availability vrrp global-parameters garp master-refresh-repeat 200", # "set high-availability vrrp global-parameters garp master-repeat 5", # "set high-availability vrrp global-parameters startup-delay 32", # "set high-availability vrrp global-parameters version 3", # "set high-availability vrrp snmp" # ] Return Values ------------- Common return values are documented `here `_, the following are the fields unique to this module: .. raw:: html
Key Returned Description
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 high-availability vrrp group g1 address '1.1.1.1'", "set high-availability vrrp group g1 advertise-interval '10'", "set high-availability vrrp group g1 description 'Group 1'"]
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 high-availability vrrp global-parameters garp master-delay '10'", "set high-availability vrrp global-parameters garp master-refresh '100'", "set high-availability vrrp global-parameters garp master-refresh-repeat '200'"]


Status ------ Authors ~~~~~~~ - Evgeny Molotkov (@omnom62) diff --git a/plugins/module_utils/network/vyos/argspec/ha/ha.py b/plugins/module_utils/network/vyos/argspec/ha/ha.py index f823bafb..4fe431cd 100644 --- a/plugins/module_utils/network/vyos/argspec/ha/ha.py +++ b/plugins/module_utils/network/vyos/argspec/ha/ha.py @@ -1,196 +1,196 @@ # -*- 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_ha module """ class HaArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_ha module""" argument_spec = { "config": { "type": "dict", "required": False, "options": { "disable": {"type": "bool", "default": False}, "virtual_servers": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "address": {"type": "str"}, "algorithm": {"type": "str"}, "delay_loop": {"type": "int"}, "forward_method": {"type": "str", "choices": ["direct", "nat"]}, "fwmark": {"type": "int"}, "persistence_timeout": {"type": "int"}, "port": {"type": "int"}, "protocol": {"type": "str", "choices": ["tcp", "udp"]}, "real_server": { "type": "list", "elements": "dict", "options": { "address": {"type": "str", "required": True}, "port": {"type": "int"}, "connection_timeout": {"type": "int"}, "health_check_script": {"type": "str"}, }, }, }, }, "vrrp": { "type": "dict", "options": { "global_parameters": { "type": "dict", "options": { "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "startup_delay": {"type": "int"}, "version": {"type": "str"}, }, }, "groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, - "address": {"type": "str"}, + "address": {"type": "list", "elements": "str"}, "advertise_interval": {"type": "int"}, "authentication": { "type": "dict", "options": { "password": {"type": "str", "no_log": True}, "type": {"type": "str"}, }, }, "description": {"type": "str"}, "disable": {"type": "bool", "default": False}, "excluded_address": {"type": "list", "elements": "str"}, "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "hello_source_address": {"type": "str"}, "interface": {"type": "str"}, "no_preempt": {"type": "bool", "default": False}, "peer_address": {"type": "str"}, "preempt_delay": {"type": "int"}, "priority": {"type": "int"}, "rfc3768_compatibility": {"type": "bool", "default": False}, "track": { "type": "dict", "options": { "exclude_vrrp_interface": {"type": "bool"}, "interface": {"type": "list", "elements": "str"}, }, }, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, "vrid": {"type": "int", "required": False}, }, }, "snmp": { "type": "str", "choices": ["disabled", "enabled"], }, "sync_groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "member": {"type": "list", "elements": "str"}, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, }, }, }, }, }, }, "state": { "type": "str", "choices": [ "deleted", "merged", "purged", "replaced", "gathered", "rendered", "parsed", "overridden", ], "default": "merged", }, "running_config": {"type": "str"}, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/ha/ha.py b/plugins/module_utils/network/vyos/config/ha/ha.py index 87e5fe44..35eeaf3b 100644 --- a/plugins/module_utils/network/vyos/config/ha/ha.py +++ b/plugins/module_utils/network/vyos/config/ha/ha.py @@ -1,638 +1,645 @@ # # -*- 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_ha 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.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_empties, ) 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.ha import ( HaTemplate, ) 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 class Ha(ResourceModule): """ The vyos_ha config class """ def __init__(self, module): super(Ha, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="ha", tmplt=HaTemplate(), ) self.parsers = [ "disable", ] def _validate_template(self): version = get_os_version(self._module) if LooseVersion(version) >= LooseVersion("1.4"): self._tmplt = HaTemplate() else: self._module.fail_json(msg="High Availability 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", "purged"]: self.generate_commands() self.run_commands() if self.state == "purged": wantd = {"disable": False} haved = deepcopy(self.have) if wantd != haved: self.commands = ["delete high-availability"] self.run_commands() - + if "before" in self.result: + self._normalize_lists(self.result["before"]) + if "after" in self.result: + self._normalize_lists(self.result["after"]) + if "parsed" in self.result: + self._normalize_lists(self.result["parsed"]) 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) for entry in wantd, haved: self._vrrp_groups_list_to_dict(entry) self._virtual_servers_list_to_dict(entry) self._vrrp_sync_groups_list_to_dict(entry) self._normalize_lists(entry) if self.state in ["deleted"]: wantd, haved, p = self._prune_stubs(self._module.params.get("config", {}), haved) if self.state in ["overridden"]: wo = deepcopy(wantd) self._diff_w_h(wo, haved) haved_disable = haved.get("disable") for k1, v1 in wo.items(): if not isinstance(v1, dict): continue for name, obj in v1.items(): if isinstance(obj, dict) and not obj: wi, hi, pi = self._prune_stubs({k1: {name: {}}}, haved) haved = hi for k2, v2 in v1.items(): if not isinstance(v2, dict): continue for name, obj in v2.items(): if isinstance(obj, dict) and not obj: wi, hi, pi = self._prune_stubs({k1: {k2: {name: {}}}}, haved) haved = hi if haved_disable is not None: haved["disable"] = haved_disable keys = set(wantd) | set(haved) for k in keys: want = wantd.get(k, {}) have = haved.get(k, {}) if k == "vrrp": if self.state in ["merged"]: - want = combine(have, want, recursive=True) + want = combine(have, want, recursive=True, list_merge="append_rp") self._compare_vrrp(want, have) if k == "virtual_servers": if self.state in ["merged"]: want = combine(have, want, recursive=True) self._compare_vsrvs(want, have) if self.state in ["deleted"] and k == "disable": want = have if self.state in ["overridden"] and k == "disable" and not want: want = False if self.state in ["rendered"]: have = None self.compare( parsers=self.parsers, want={k: want}, have={k: have}, ) self.commands = sorted(list(dict.fromkeys(self.commands))) def _compare_vsrvs(self, want, have): """Compare virtual servers of VRRP""" vs_parsers = [ "virtual_servers.address", "virtual_servers.algorithm", "virtual_servers.delay_loop", "virtual_servers.forward_method", "virtual_servers.persistence_timeout", "virtual_servers.fwmark", "virtual_servers.port", "virtual_servers.protocol", "virtual_servers.real_server.port", "virtual_servers.real_server.health_check_script", "virtual_servers.real_server.connection_timeout", ] hlist = self._extract_named_leafs(have) wlist = self._extract_named_leafs(want) if self.state == "rendered": hlist = [] if self.state in ["replaced", "deleted"]: for hdict in hlist: wdict = self._find_matching_vsrv(hdict, wlist) if self.state == "deleted" and wdict: wdict = {} elif not wdict: hdict = {} self.compare( parsers=vs_parsers, want={"virtual_servers": wdict}, have={"virtual_servers": hdict}, ) if self.state in ["merged", "replaced", "rendered", "overridden"]: for wdict in wlist: hdict = self._find_matching_vsrv(wdict, hlist) self.compare( parsers=vs_parsers, want={"virtual_servers": wdict}, have={"virtual_servers": hdict}, ) def _compare_vrrp(self, want, have): """Compare the instances of VRRP""" vrrp_parsers = [ "vrrp.snmp", "vrrp.global_parameters", "vrrp.global_parameters.garp", "vrrp.groups", "vrrp.groups.disable", "vrrp.groups.no_preempt", "vrrp.groups.rfc3768_compatibility", + "vrrp.groups.address", "vrrp.groups.excluded_address", "vrrp.groups.garp", "vrrp.groups.authentication", "vrrp.groups.transition_script", "vrrp.groups.health_check", "vrrp.groups.track.interface", "vrrp.groups.track.exclude_vrrp_interface", "vrrp.sync_groups.member", "vrrp.sync_groups.transition_script", "vrrp.sync_groups.health_check", ] if ( have.get("snmp") == "enabled" and want.get("snmp") != "enabled" and self.state not in ["deleted", "overridden"] ): self.commands.append("delete high-availability vrrp snmp") hlist = self._extract_leaf_items(have) wlist = self._extract_leaf_items(want) if self.state == "rendered": hlist = [] if self.state in ["replaced", "deleted"]: for hdict in hlist: wdict = self._find_matching_vrrp(hdict, wlist) if self.state == "deleted" and wdict: wdict = {} elif not wdict: hdict = {} self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) if self.state in ["merged", "replaced", "rendered", "overridden"]: + for wdict in wlist: hdict = self._find_matching_vrrp(wdict, hlist) self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) def _vrrp_groups_list_to_dict(self, data): vrrp = data.get("vrrp", {}) groups = vrrp.get("groups") if not groups: return data if isinstance(groups, dict): return data if isinstance(groups, list): new_groups = {} for item in groups: name = item.get("name") if not name: continue new_groups[name] = item data["vrrp"]["groups"] = new_groups return data return data def _vrrp_sync_groups_list_to_dict(self, data): vrrp = data.get("vrrp", {}) groups = vrrp.get("sync_groups") if not groups: return data if isinstance(groups, dict): return data if isinstance(groups, list): new_groups = {} for item in groups: name = item.get("name") if not name: continue new_groups[name] = item data["vrrp"]["sync_groups"] = new_groups return data return data def _virtual_servers_list_to_dict(self, data): vss = data.get("virtual_servers") if not vss: return data if isinstance(vss, dict): for vs in vss.items(): rs = vs.get("real_server") if isinstance(rs, list): vs["real_server"] = { item["address"]: item for item in rs if isinstance(item, dict) and item.get("address") } return data if isinstance(vss, list): new_vss = {} for vs in vss: if not isinstance(vs, dict): continue name = vs.get("name") if not name: continue rs = vs.get("real_server") if isinstance(rs, list): vs["real_server"] = { item["address"]: item for item in rs if isinstance(item, dict) and item.get("address") } new_vss[name] = vs data["virtual_servers"] = new_vss return data return data def _extract_leaf_items(self, data, path=None, parent_name=None): path = path or [] results = [] if isinstance(data, dict): current_name = data.get("name", parent_name) for k, v in data.items(): if k == "name" or (k == "snmp" and v == "disabled"): continue results.extend(self._extract_leaf_items(v, path + [k], current_name)) return results leaf_key = path[-1] top_key = path[0] if top_key in ["groups", "sync_groups"]: subkeys = path[2:] else: subkeys = path[1:] nested = {leaf_key: data} for p in reversed(subkeys[:-1]): nested = {p: nested} if parent_name: out = {top_key: {"name": parent_name}} out[top_key].update(nested) else: out = {top_key: nested} results.append(out) return results def _find_matching_vsrv(self, want_item, have_list): def build_sig(item): if not isinstance(item, dict): return None name = item.get("name") if not name: return None if "real_server" in item: rs = item["real_server"] if not isinstance(rs, dict) or "address" not in rs: return None addr = rs["address"] for k in rs: if k != "address": return ("real_server", name, addr, k) return ("real_server", name, addr, None) for k in item: if k != "name": return ("attr", name, k) return None sig_want = build_sig(want_item) for obj in have_list: if build_sig(obj) == sig_want: return obj return {} def _find_matching_vrrp(self, want_item, have_list): def build_sig(item): if not isinstance(item, dict) or not item: return () container = next(iter(item)) inner = item[container] sig = [container] if isinstance(inner, dict) and "name" in inner: sig.append(("name", inner["name"])) if isinstance(inner, dict): for k, v in inner.items(): if k == "name": continue if not isinstance(v, dict): sig.append(k) break sig.append(k) for leaf in v: sig.append(leaf) break break return tuple(sig) sig_want = build_sig(want_item) for obj in have_list: if build_sig(obj) == sig_want: return obj return {} def _normalize_lists(self, node): """ Recursively normalize all lists inside a dict or list. All lists are sorted to ensure consistent ordering for comparison. """ if isinstance(node, dict): for k, v in node.items(): if isinstance(v, list): if all(not isinstance(i, (dict, list)) for i in v): node[k] = sorted(v) else: for item in v: self._normalize_lists(item) elif isinstance(v, dict): self._normalize_lists(v) elif isinstance(node, list): for item in node: self._normalize_lists(item) def _extract_named_leafs(self, data, parent_name=None, prefix_key=None): results = [] if prefix_key == "real_server" and isinstance(data, dict): for d, server_data in data.items(): if not isinstance(server_data, dict): continue address = server_data.get("address") if not address: continue for k, v in server_data.items(): if k == "address": continue results.append( { "name": parent_name, "real_server": { "address": address, k: v, }, }, ) return results if isinstance(data, dict): current_name = data.get("name", parent_name) for k, v in data.items(): if k == "name": continue results.extend( self._extract_named_leafs(v, current_name, k), ) return results return [ { "name": parent_name, prefix_key: data, }, ] def _prune_stubs(self, w, h, path=""): wc = {} hc = self._remove_defaults(h) if not self._remove_defaults(w) and remove_empties(hc): self.commands = ["delete high-availability"] return {}, {}, path for k, wg in (self._remove_defaults(w) or {}).items(): next_path = f"{path} {k}".strip() stub = self._cli_path(next_path) hg = remove_empties(hc).get(k) if hg is None: continue if not isinstance(wg, (dict, list)): self.commands.append(f"delete high-availability {stub}") hc.pop(k, None) wc.pop(k, None) continue if not wg: self.commands.append(f"delete high-availability {stub}") hc.pop(k, None) wc.pop(k, None) continue if isinstance(wg, list) and isinstance(hg, dict): for item in wg: name = item.get("name") if not name: continue if name in hg: self.commands.append( f"delete high-availability {stub} {name}", ) hg.pop(name, None) if hg: hc[k] = hg else: hc.pop(k, None) if self._remove_defaults(wg): wc[k] = wg else: wc.pop(k, None) continue if isinstance(wg, dict) and isinstance(hg, dict): wi, hi, p = self._prune_stubs(wg, hg, next_path) if wi: wc[k] = wi if hi: hc[k] = hi else: hc.pop(k, None) return wc, hc, path def _remove_defaults(self, data): if isinstance(data, dict): cleaned = {} for k, v in data.items(): if v in [None, False, "disabled"]: continue v = self._remove_defaults(v) cleaned[k] = v return cleaned return data def _cli_path(self, path): token_map = { "groups": "group", "sync_groups": "sync-group", "virtual_servers": "virtual-server", } parts = [] for p in path.split(): p = token_map.get(p, p) parts.append(p.replace("_", "-")) return " ".join(parts) def _diff_w_h(self, w, h): NAMED_OBJECT_KEYS = { "groups", "sync_groups", "virtual_servers", "global_parameters", } if not isinstance(w, dict) or not isinstance(h, dict): return w for key in w.keys() & h.keys(): wv = w[key] hv = h[key] if key in NAMED_OBJECT_KEYS and isinstance(wv, dict) and isinstance(hv, dict): for name in wv.keys() & hv.keys(): if wv[name] != hv[name] and isinstance(wv[name], (dict, list)): wv[name] = {} elif wv[name] != hv[name]: wv[name] = None continue self._diff_w_h(wv, hv) return w diff --git a/plugins/module_utils/network/vyos/facts/ha/ha.py b/plugins/module_utils/network/vyos/facts/ha/ha.py index 036b5088..0cd71592 100644 --- a/plugins/module_utils/network/vyos/facts/ha/ha.py +++ b/plugins/module_utils/network/vyos/facts/ha/ha.py @@ -1,148 +1,164 @@ # -*- 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_ha 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.ha.ha import ( HaArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ha import ( HaTemplate, ) class HaFacts(object): """The vyos_ha facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = HaArgs.argument_spec def get_config(self, connection): return connection.get('show configuration commands | match "set high-availability"') def get_config_set(self, data, connection): """To classify the configurations beased on high availability sections""" config_dict = {} for config_line in data.splitlines(): vrrp_grp = re.search(r"set high-availability vrrp group (\S+).*", config_line) vrrp_gp = re.search( r"set high-availability vrrp global-parameters (\S+).*", config_line, ) vrrp_sg = re.search(r"set high-availability vrrp sync-group (\S+).*", config_line) vrrp_vsrv = re.search(r"set high-availability virtual-server (\S+).*", config_line) vrrp_disable = re.search(r"set high-availability disable", config_line) vrrp_snmp = re.search(r"set high-availability vrrp snmp", config_line) if vrrp_disable: config_dict["disable"] = [config_dict.get("disable", "") + config_line] if vrrp_snmp: config_dict.setdefault("vrrp", []).append(config_line) if vrrp_gp: config_dict.setdefault("global_parameters", []).append(config_line) if vrrp_vsrv: config_dict.setdefault(vrrp_vsrv.group(1), []).append(config_line) if vrrp_sg: config_dict.setdefault(vrrp_sg.group(1), []).append(config_line) if vrrp_grp: config_dict.setdefault(vrrp_grp.group(1), []).append(config_line) return list(config_dict.values()) def deep_merge(self, dest, src): for key, value in src.items(): if key in dest and isinstance(dest[key], dict) and isinstance(value, dict): self.deep_merge(dest[key], value) else: dest[key] = value return dest def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for vrrp 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) resources = self.get_config_set(data, connection) vrrp_facts = {"disable": False, "virtual_servers": {}, "vrrp": {}} for resource in resources: vrrp_parser = HaTemplate( lines=resource, module=self._module, ) objs = vrrp_parser.parse() if "disable" in objs: vrrp_facts["disable"] = objs["disable"] for section in ("virtual_servers", "vrrp"): if section in objs: for name, data in objs[section].items(): if not isinstance(data, dict): vrrp_facts[section][name] = data continue existing = vrrp_facts[section].get(name, {}) vrrp_facts[section][name] = self.deep_merge(existing, data) ansible_facts["ansible_network_resources"].pop("vrrp", None) vrrp_facts = self.normalize_config(vrrp_facts) validate_parser = HaTemplate(lines=[], module=self._module) params = utils.remove_empties( validate_parser.validate_config( self.argument_spec, {"config": vrrp_facts}, redact=True, ), ) - facts["ha"] = params.get("config", []) + facts["ha"] = self.normalize_config(params.get("config", [])) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def normalize_config(self, config): if not config: return config # Normalize virtual_servers if isinstance(config.get("virtual_servers"), dict): config["virtual_servers"] = list(config["virtual_servers"].values()) # Normalize vrrp vrrp = config.get("vrrp", {}) if isinstance(vrrp.get("groups"), dict): vrrp["groups"] = list(vrrp["groups"].values()) if isinstance(vrrp.get("sync_groups"), dict): vrrp["sync_groups"] = list(vrrp["sync_groups"].values()) # Normalize real_server inside each virtual_server for vs in config.get("virtual_servers", []): if isinstance(vs.get("real_server"), dict): vs["real_server"] = list(vs["real_server"].values()) + vrrp = config.get("vrrp", {}) + + for group in vrrp.get("groups", []): + if isinstance(group.get("address"), list): + group["address"] = sorted(group["address"]) + + if isinstance(group.get("excluded_address"), list): + group["excluded_address"] = sorted(group["excluded_address"]) + + if isinstance(group.get("track", {}).get("interface"), list): + group["track"]["interface"] = sorted(group["track"]["interface"]) + + for sg in vrrp.get("sync_groups", []): + if isinstance(sg.get("member"), list): + sg["member"] = sorted(sg["member"]) + return config diff --git a/plugins/module_utils/network/vyos/rm_templates/ha.py b/plugins/module_utils/network/vyos/rm_templates/ha.py index d803f72a..dc7c0c86 100644 --- a/plugins/module_utils/network/vyos/rm_templates/ha.py +++ b/plugins/module_utils/network/vyos/rm_templates/ha.py @@ -1,972 +1,1010 @@ # -*- 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 Ha parser templates file. This contains a list of parser definitions and associated functions that facilitates both facts gathering and native command generation for the given network resource. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) def _tmplt_vsrvs(config_data): config_data = config_data["virtual_servers"] command = [] cmd = "high-availability virtual-server {name}".format(**config_data) for key, value in config_data.items(): if key == "name" or isinstance(value, dict) or value is None: continue else: command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vsrvs_rsrv(config_data): config_data = config_data["virtual_servers"] command = [] cmd = "high-availability virtual-server {name}".format(**config_data) config_data = config_data["real_server"] address = config_data["address"] for key, value in config_data.items(): if key == "address" or value is None: continue if value is not None and key == "health_check_script": command.append(cmd + " real-server " + address + " health-check script " + value) else: command.append(cmd + " real-server " + f"{address} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_hc(config_data): config_data = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**config_data) config_data = config_data["health_check"] for key, value in config_data.items(): if value is not None: command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_ts(config_data): config_data = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**config_data) config_data = config_data["transition_script"] for key, value in config_data.items(): if value is not None: command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_gp(config_data): config_data = config_data["vrrp"]["global_parameters"] command = [] cmd = "high-availability vrrp global-parameters".format(**config_data) for key, value in config_data.items(): if isinstance(value, dict) or value is None: continue else: command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_gp_garp(config_data): config_data = config_data["vrrp"]["global_parameters"]["garp"] command = [] cmd = "high-availability vrrp global-parameters garp" for key, value in config_data.items(): if value is None: continue command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) for key, value in config_data.items(): if ( key == "name" or isinstance(value, dict) or isinstance(value, list) or isinstance(value, bool) or value is None ): continue else: if key == "description": value = f"'{value}'" command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_bool(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) for key, value in config_data.items(): if key != "name" and value is not None: command.append(f"{cmd} {key.replace('_', '-')}") return command def _tmplt_vrrp_group_garp(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["garp"] for key, value in config_data.items(): if value is not None: command.append(cmd + " garp " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_auth(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["authentication"] for key, value in config_data.items(): if value is not None: command.append(cmd + " authentication " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_ts(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["transition_script"] for key, value in config_data.items(): if value is not None: command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_member(config_data): sgroup = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**sgroup) members = sgroup.get("member", []) for member in members: if member is None: continue command.append(f"{cmd} member {member}") return command def _tmplt_vrrp_group_exaddress(config_data): group = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**group) exaddresses = group.get("excluded_address", []) for exaddress in exaddresses: if exaddress is None: continue command.append(f"{cmd} excluded-address {exaddress}") return command +def _tmplt_vrrp_group_address(config_data): + group = config_data["vrrp"]["groups"] + command = [] + cmd = "high-availability vrrp group {name}".format(**group) + addresses = group.get("address", []) + for address in addresses: + if address is None: + continue + command.append(f"{cmd} address {address}") + return command + + def _tmplt_vrrp_group_hc(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["health_check"] for key, value in config_data.items(): if value is not None: command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_track_list(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["track"] for key, value in config_data.items(): if isinstance(value, list) and value is not None and key != "name": for item in value: command.append(cmd + " track " + f"{key.replace('_', '-')} {item}") return command def _tmplt_vrrp_group_track_bool(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["track"] for key, value in config_data.items(): if key != "name" and value is not None: command.append(cmd + " track " + f"{key.replace('_', '-')}") return command class HaTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(HaTemplate, self).__init__( lines=lines, tmplt=self, prefix=prefix, module=module, ) # fmt: off PARSERS = [ { "name": "disable", "getval": re.compile( r""" ^set \shigh-availability \s(?Pdisable) $""", re.VERBOSE, ), "setval": "high-availability disable", "result": { "disable": "{{ True if disable is defined else False }}", }, }, { "name": "virtual_servers.address", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+address\s+(?P
\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "address": "{{ address if address is defined else None }}", }, }, }, }, { "name": "virtual_servers.algorithm", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+algorithm\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "algorithm": "{{ algorithm if algorithm is defined else None }}", }, }, }, }, { "name": "virtual_servers.delay_loop", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+delay-loop\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "delay_loop": "{{ delay_loop if delay_loop is defined else None }}", }, }, }, }, { "name": "virtual_servers.forward_method", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+forward-method\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "forward_method": "{{ forward_method if forward_method is defined else None }}", }, }, }, }, { "name": "virtual_servers.fwmark", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+fwmark\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "fwmark": "{{ fwmark if fwmark is defined else None }}", }, }, }, }, { "name": "virtual_servers.persistence_timeout", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+persistence-timeout\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "persistence_timeout": "{{ persistence_timeout if persistence_timeout is defined else None }}", }, }, }, }, { "name": "virtual_servers.port", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+port\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "port": "{{ port if port is defined else None }}", }, }, }, }, { "name": "virtual_servers.protocol", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+protocol\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "protocol": "{{ protocol if protocol is defined else None }}", }, }, }, }, { "name": "virtual_servers.real_server.port", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) \sreal-server \s+(?P
\S+) (?:\s+port\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs_rsrv, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "real_server": { "{{ address }}": { "address": "{{ address }}", "port": "{{ port if port is defined else None }}", }, }, }, }, }, }, { "name": "virtual_servers.real_server.health_check_script", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) \sreal-server \s+(?P
\S+) (?:\s+health-check\sscript\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs_rsrv, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "real_server": { "{{ address }}": { "address": "{{ address }}", "health_check_script": "{{ hcscript if hcscript is defined else None }}", }, }, }, }, }, }, { "name": "virtual_servers.real_server.connection_timeout", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) \sreal-server \s+(?P
\S+) (?:\s+connection-timeout\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs_rsrv, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "real_server": { "{{ address }}": { "address": "{{ address }}", "connection_timeout": "{{ cont if cont is defined else None }}", }, }, }, }, }, }, { "name": "vrrp.sync_groups.member", "getval": re.compile( r""" ^set\shigh-availability\svrrp\ssync-group \s+(?P\S+) \smember \s+(?P\S+) $ """, re.VERBOSE, ), "setval": _tmplt_vrrp_sgroup_member, "result": { "vrrp": { "sync_groups": { "{{ sgname }}": { "name": "{{ sgname }}", "member": [ "{{ member }}", ], }, }, }, }, }, { "name": "vrrp.sync_groups.health_check", "getval": re.compile( r""" ^set\shigh-availability\svrrp\ssync-group \s+(?P\S+) \shealth-check (?:\s+failure-count\s+(?P\S+)) ?(?:\s+interval\s+(?P\S+)) ?(?:\s+ping\s+(?P\S+)) ?(?:\s+script\s+(?P