diff options
| author | omnom62 <75066712+omnom62@users.noreply.github.com> | 2026-08-19 22:21:24 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-19 15:21:24 +0300 |
| commit | a6adced609472d88cb525f2ef1a4054b52692f76 (patch) | |
| tree | de206d2bbc56c970c2db3162cdee3d78b341c811 | |
| parent | b6b58bc747c4ea59371253c5541cde92b922c173 (diff) | |
| download | vyos.vyos-a6adced609472d88cb525f2ef1a4054b52692f76.tar.gz vyos.vyos-a6adced609472d88cb525f2ef1a4054b52692f76.zip | |
T6820: HA/VRRP module (#438)
* Init VRRP
* RM Templates
* rm_templates for global_parameters
* virtual servers rm_templates
* update
* sync-group rm_tempalte
* VRRP GP GARP template rework
* RM template and new facts logic
* vrrp argspec
* Suppressing backup versions of files
* Facts for vrrp
* VRRP facts updates
* Facts almost done
* Argspec vrrp
* Argspec completed
* Argspec and facts for VRRP finished
* Documentation for VRRP module
* Sanity tests fixes and documentation
* VRRP config started
* Doc fixes
* Doc fixes
* Doc fixes
* Configuration for VRRP
* wip
* Updates to facts, config, templates in VRRP
* VRRP groups templates, facts and config
* VRRP works - templates for groups and virtual_servers
* Mid-result
* VRRP VS prototype
* Groups processor update
* Reworked vrrp.group and vrrp.group.garp processors
* VRRP grp recursive
* VRRP groups and vserver contd
* Update to VRRP want processing
* Updates to VRRP funcs
* VRRP VS RS config
* Sanity test fixes midway
* VRRP Real server fixes
* VRRP sync_group
* Latest fixes to etire VRRP config parsing
* Midway compare function
* update to vrrp group processing
* Mid results for merge parsers / compare
* Working merge for vrrp and virtserv
* Final merge / compare
* WIP replaced / Delete
* Updates to state processing
* Mid-result for virtual_servers
* Mid result commit
* Testing high-availability vrrp processing
* Retesting vrrp stanza
* Mid results
* Real server parsing
* SNMP vrrp fixed
* Sanity fixes
* Purged, rendered, gathered, parsed action states for VRRP
* VRRP integration and unit tests; code fixes midway results
* Sanity check fixes
* Merged integration test case for VRRP
* VRRP rendered integration test case
* Sanity tests fixes
* VRRP unit tests - merged and purged
* Mid-way replaced, deleted and overriden work. Description string fix
* Updates to integration and unit tests for VRRP
* Mid-result for deleted state VRRP
* Mid-results for VRRP deleted
* Deleted vrrp midway results
* Deleted state function for VRRP
* Deleted state VRRP, sanity checks fix
* Deleted integration tests
* Deleted_all unit and integration tests
* Deleted_all unit and integration tests
* Sanity check fixes
* Mid-results overridden VRRP
* WIP vrrp overridden sit and uat
* VRRP overridden sanity test
* UAT overriden vrrp
* UAT deleted for vrrp
* VRRP replaced UAT and SIT
* VRRP RTT SIT
* VRRP SIT suite ready
* Finals for VRRP module - clean-up and fixed UAT
* VRRP module doc examples updated
* VRRP sanity tests fixes
* VRRP disbale default fix
* Misc lint fixes
* Misc Lint fixes
* remove old files
* Rename vyos_vrrp -> vyos_ha
* Rename vyos_vrrp to vyos_ha
* doc fixes
* Changes to HA module
* HA module fixes
* Sanity test fixes
* changelog
* ignore for HA/VRRP and 2.22
* AI-generated comments and fixes
* Fixes after review
* Misc fixes
* Misc fixes
* Misc fixes
* Misc fixes
* Sanity fixes
* Sanity fixes
* Misc changes
* T6820: code refactor to address valid AI comments
* T6820: Optimising facts, config and rm_templates
* T6820: UAT fixes
* T6820: docstring for utils/combine()
* T6820 Misc fixes to changelog
---------
Co-authored-by: Gaige B Paulsen <gaige@cluetrust.com>
46 files changed, 8833 insertions, 8 deletions
@@ -114,6 +114,7 @@ venv.bak/ *.code-workspace .vscode/ .DS_Store +*.bak changelogs/.plugin-cache.yaml # inventory for testing @@ -52,6 +52,7 @@ Name | Description [vyos.vyos.vyos_firewall_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_global_module.rst)|Firewall global resource module [vyos.vyos.vyos_firewall_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_interfaces_module.rst)|Firewall interfaces resource module [vyos.vyos.vyos_firewall_rules](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_rules_module.rst)|Firewall rules resource module +[vyos.vyos.vyos_ha](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ha_module.rst)|Manage VRRP and load balancer configuration on VyOS [vyos.vyos.vyos_hostname](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_hostname_module.rst)|Manages hostname resource module [vyos.vyos.vyos_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interfaces_module.rst)|Manages interface attributes of VyOS network devices. [vyos.vyos.vyos_l3_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_l3_interfaces_module.rst)|Layer 3 interfaces resource module. diff --git a/changelogs/fragments/t6820_vrrp.yml b/changelogs/fragments/t6820_vrrp.yml new file mode 100644 index 00000000..94145121 --- /dev/null +++ b/changelogs/fragments/t6820_vrrp.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vyos_ha - Add VRRP (High Availability) support. diff --git a/docs/vyos.vyos.vyos_ha_module.rst b/docs/vyos.vyos.vyos_ha_module.rst new file mode 100644 index 00000000..5e03661c --- /dev/null +++ b/docs/vyos.vyos.vyos_ha_module.rst @@ -0,0 +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 + + <table border=0 cellpadding=0 class="documentation-table"> + <tr> + <th colspan="5">Parameter</th> + <th>Choices/<font color="blue">Defaults</font></th> + <th width="100%">Comments</th> + </tr> + <tr> + <td colspan="5"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>config</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Full VRRP and virtual server configuration.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td colspan="4"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>disable</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li><div style="color: blue"><b>no</b> ←</div></li> + <li>yes</li> + </ul> + </td> + <td> + <div>Disable all VRRP and L4-LB configuration under this module.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td colspan="4"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>virtual_servers</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>List of load balancer virtual server (LVS) definitions.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Virtual IP address for the server.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>algorithm</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Load balancing algorithm used for dispatching connections.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>delay_loop</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Delay loop interval in seconds.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>forward_method</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>direct</li> + <li>nat</li> + </ul> + </td> + <td> + <div>Forwarding method used by LVS.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>fwmark</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Firewall mark for LVS traffic classification.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>name</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + / <span style="color: red">required</span> + </div> + </td> + <td> + </td> + <td> + <div>Unique identifier for the virtual server.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>persistence_timeout</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Client persistence timeout in seconds.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>port</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>TCP/UDP port provided by the virtual service.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>protocol</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>tcp</li> + <li>udp</li> + </ul> + </td> + <td> + <div>Transport protocol for the virtual server.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>real_server</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Backend real servers behind the virtual service.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + / <span style="color: red">required</span> + </div> + </td> + <td> + </td> + <td> + <div>Real server IP address.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>connection_timeout</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Backend server connection timeout.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>health_check_script</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Path to health check script used for backend validation.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>port</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Backend server port.</div> + </td> + </tr> + + + <tr> + <td class="elbow-placeholder"></td> + <td colspan="4"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>vrrp</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP configuration including groups, global parameters, SNMP settings, and sync-groups.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>global_parameters</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Global VRRP tuning parameters.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>garp</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Gratuitous ARP related configuration.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interval</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP interval in seconds.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_delay</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Delay before sending GARP as master.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_refresh</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Refresh interval for master GARP announcements.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_refresh_repeat</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Number of times to repeat refresh announcements.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_repeat</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Number of GARP repeats when transitioning to master.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>startup_delay</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Delay before VRRP starts after boot.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>version</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP protocol version.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>groups</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP instance configuration groups.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>Virtual router IP addresses.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>advertise_interval</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP advertisement interval.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>authentication</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP group authentication options.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>password</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Authentication password.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>type</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Authentication type.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>description</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Text description for the VRRP group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>disable</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li><div style="color: blue"><b>no</b> ←</div></li> + <li>yes</li> + </ul> + </td> + <td> + <div>Disable this VRRP group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>excluded_address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>IP address excluded from source checks.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>garp</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP-specific settings for this group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interval</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP interval.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_delay</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP master delay.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_refresh</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP master refresh interval.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_refresh_repeat</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Repeated refresh sends.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master_repeat</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>GARP repeat count.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>health_check</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP group health check options.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>failure_count</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Allowed number of failed checks.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interval</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Health check interval.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>ping</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Host to ping for checks.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>script</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Script to execute for health checking.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>hello_source_address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Source address for VRRP hello packets.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interface</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Interface used by the VRRP group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>name</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + / <span style="color: red">required</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP group name.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>no_preempt</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li><div style="color: blue"><b>no</b> ←</div></li> + <li>yes</li> + </ul> + </td> + <td> + <div>Disable preemption.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>peer_address</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Peer VRRP router address.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>preempt_delay</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Delay before taking master role.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>priority</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP priority (higher = preferred master).</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>rfc3768_compatibility</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li><div style="color: blue"><b>no</b> ←</div></li> + <li>yes</li> + </ul> + </td> + <td> + <div>Enable or disable RFC3768 compatibility mode.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>track</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Track interface and VRRP behaviour.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>exclude_vrrp_interface</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>no</li> + <li>yes</li> + </ul> + </td> + <td> + <div>Exclude VRRP interface from tracking.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interface</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>Interface to track.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>transition_script</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Scripts executed during VRRP state transitions.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>backup</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Path to backup script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>fault</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Path to fault script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Path to master script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>stop</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Path to stop script.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>vrid</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP Virtual Router ID.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>snmp</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>enabled</li> + <li>disabled</li> + </ul> + </td> + <td> + <div>Enable SNMP support for VRRP.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>sync_groups</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>VRRP sync-groups for coordinated failover.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>health_check</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Health check options for sync group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>failure_count</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Allowed number of failures.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>interval</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">integer</span> + </div> + </td> + <td> + </td> + <td> + <div>Health check interval.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>ping</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Host to ping.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>script</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Script to run for health checking.</div> + </td> + </tr> + + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>member</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>List of VRRP groups participating in this sync group.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>name</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + / <span style="color: red">required</span> + </div> + </td> + <td> + </td> + <td> + <div>Sync-group name.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="2"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>transition_script</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td> + </td> + <td> + <div>Transition scripts for sync group events.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>backup</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Backup state script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>fault</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Fault state script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>master</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Master state script.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>stop</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Stop state script.</div> + </td> + </tr> + + + + + <tr> + <td colspan="5"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>running_config</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Used only when <code>state=parsed</code>. Must contain the output of <code>show configuration commands | grep high-availability</code>.</div> + </td> + </tr> + <tr> + <td colspan="5"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>state</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>deleted</li> + <li><div style="color: blue"><b>merged</b> ←</div></li> + <li>purged</li> + <li>replaced</li> + <li>gathered</li> + <li>rendered</li> + <li>parsed</li> + <li>overridden</li> + </ul> + </td> + <td> + <div>Desired end state of the VRRP configuration.</div> + </td> + </tr> + </table> + <br/> + + + + +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 <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module: + +.. raw:: html + + <table border=0 cellpadding=0 class="documentation-table"> + <tr> + <th colspan="1">Key</th> + <th>Returned</th> + <th width="100%">Description</th> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>after</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td>when changed</td> + <td> + <div>The resulting configuration after module execution.</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>before</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td>when <em>state</em> is <code>merged</code>, <code>replaced</code>, <code>overridden</code>, <code>deleted</code> or <code>purged</code></td> + <td> + <div>The configuration prior to the module execution.</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>commands</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + </div> + </td> + <td>when <em>state</em> is <code>merged</code>, <code>replaced</code>, <code>overridden</code>, <code>deleted</code> or <code>purged</code></td> + <td> + <div>The set of commands pushed to the remote device.</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">["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'"]</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>gathered</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + </div> + </td> + <td>when <em>state</em> is <code>gathered</code></td> + <td> + <div>Facts about the network resource gathered from the remote device as structured data.</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>parsed</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + </div> + </td> + <td>when <em>state</em> is <code>parsed</code></td> + <td> + <div>The device native config provided in <em>running_config</em> option parsed into structured data as per module argspec.</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>rendered</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + </div> + </td> + <td>when <em>state</em> is <code>rendered</code></td> + <td> + <div>The provided configuration in the task rendered in device-native format (offline).</div> + <br/> + <div style="font-size: smaller"><b>Sample:</b></div> + <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">["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'"]</div> + </td> + </tr> + </table> + <br/><br/> + + +Status +------ + + +Authors +~~~~~~~ + +- Evgeny Molotkov (@omnom62) diff --git a/meta/runtime.yml b/meta/runtime.yml index fe819fdf..9bee831f 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -68,3 +68,5 @@ plugin_routing: redirect: vyos.vyos.vyos_vlan vrf: redirect: vyos.vyos.vyos_vrf + ha: + redirect: vyos.vyos.vyos_ha diff --git a/plugins/action/ha.py b/plugins/action/ha.py new file mode 120000 index 00000000..331a791f --- /dev/null +++ b/plugins/action/ha.py @@ -0,0 +1 @@ +vyos.py
\ No newline at end of file diff --git a/plugins/module_utils/network/vyos/argspec/ha/__init__.py b/plugins/module_utils/network/vyos/argspec/ha/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ha/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/ha/ha.py b/plugins/module_utils/network/vyos/argspec/ha/ha.py new file mode 100644 index 00000000..4fe431cd --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ha/ha.py @@ -0,0 +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": "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/__init__.py b/plugins/module_utils/network/vyos/config/ha/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ha/__init__.py diff --git a/plugins/module_utils/network/vyos/config/ha/ha.py b/plugins/module_utils/network/vyos/config/ha/ha.py new file mode 100644 index 00000000..7fc96a35 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ha/ha.py @@ -0,0 +1,705 @@ +# +# -*- 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", + ] + + self._validate_template() + + 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 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 = deepcopy(self.want) + haved = deepcopy(self.have) + for entry in wantd, haved: + self._list_to_named_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, 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 = list(dict.fromkeys(self.commands)) + + def _compare_vsrvs(self, want, have): + """Compare virtual servers. + + Pre-index both want and have by (name, attribute) signature so that + each lookup is O(1) instead of O(n). Groups that are identical + between want and have are skipped entirely via an equality + short-circuit before leaf decomposition. + """ + 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", + ] + + want_index = ( + {vs["name"]: vs for vs in want.values() if isinstance(vs, dict) and vs.get("name")} + if isinstance(want, dict) + else {} + ) + have_index = ( + {vs["name"]: vs for vs in have.values() if isinstance(vs, dict) and vs.get("name")} + if isinstance(have, dict) + else {} + ) + + all_names = set(want_index) | set(have_index) + + for name in all_names: + w = want_index.get(name, {}) + h = have_index.get(name, {}) + + if w == h and self.state not in ["rendered"]: + continue + + wlist = self._extract_named_leafs(w) if w else [] + hlist = self._extract_named_leafs(h) if h else [] + + if self.state == "rendered": + hlist = [] + + def _vsrv_sig(item): + if not isinstance(item, dict): + return None + iname = item.get("name") + if not iname: + 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", iname, addr, k) + return ("real_server", iname, addr, None) + for k in item: + if k != "name": + return ("attr", iname, k) + return None + + have_leaf_index = {} + for hdict in hlist: + sig = _vsrv_sig(hdict) + if sig is not None: + have_leaf_index[sig] = hdict + + want_leaf_index = {} + for wdict in wlist: + sig = _vsrv_sig(wdict) + if sig is not None: + want_leaf_index[sig] = wdict + + if self.state in ["replaced", "deleted"]: + for sig, hdict in have_leaf_index.items(): + wdict = want_leaf_index.get(sig, {}) + 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 sig, wdict in want_leaf_index.items(): + hdict = have_leaf_index.get(sig, {}) + self.compare( + parsers=vs_parsers, + want={"virtual_servers": wdict}, + have={"virtual_servers": hdict}, + ) + + def _compare_vrrp(self, want, have): + """Compare VRRP groups and sync-groups. + + Pre-index groups by name so matching is O(1). Groups that are + identical between want and have are skipped via equality + short-circuit before any leaf decomposition occurs — this is the + dominant performance win for large idempotent runs. + """ + 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"] + and (self.state != "merged" or "snmp" in want) + ): + self.commands.append("delete high-availability vrrp snmp") + + non_named = {k: v for k, v in (want or {}).items() if k not in ("groups", "sync_groups")} + non_named_have = { + k: v for k, v in (have or {}).items() if k not in ("groups", "sync_groups") + } + + hlist_non = self._extract_leaf_items(non_named_have) + wlist_non = self._extract_leaf_items(non_named) + + if self.state == "rendered": + hlist_non = [] + + have_non_index = {} + for hdict in hlist_non: + sig = self._vrrp_leaf_sig(hdict) + have_non_index[sig] = hdict + + want_non_index = {} + for wdict in wlist_non: + sig = self._vrrp_leaf_sig(wdict) + want_non_index[sig] = wdict + + if self.state in ["replaced", "deleted"]: + for sig, hdict in have_non_index.items(): + wdict = want_non_index.get(sig, {}) + if self.state == "deleted" and wdict: + wdict = {} + if self.state == "replaced" and wdict and wdict != hdict: + 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 sig, wdict in want_non_index.items(): + hdict = have_non_index.get(sig, {}) + self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) + + for section in ("groups", "sync_groups"): + want_objs = (want or {}).get(section, {}) + have_objs = (have or {}).get(section, {}) + + if not isinstance(want_objs, dict): + want_objs = {} + if not isinstance(have_objs, dict): + have_objs = {} + + all_names = set(want_objs) | set(have_objs) + + for name in all_names: + w = want_objs.get(name, {}) + h = have_objs.get(name, {}) + + if w == h and self.state not in ["rendered"]: + continue + + wlist = self._extract_leaf_items({section: {name: w}}) if w else [] + hlist = self._extract_leaf_items({section: {name: h}}) if h else [] + + if self.state == "rendered": + hlist = [] + + have_leaf_index = {} + for hdict in hlist: + sig = self._vrrp_leaf_sig(hdict) + have_leaf_index[sig] = hdict + + want_leaf_index = {} + for wdict in wlist: + sig = self._vrrp_leaf_sig(wdict) + want_leaf_index[sig] = wdict + + if self.state in ["replaced", "deleted"]: + for sig, hdict in have_leaf_index.items(): + wdict = want_leaf_index.get(sig, {}) + if self.state == "deleted" and wdict: + wdict = {} + if self.state == "replaced" and wdict and wdict != hdict: + 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 sig, wdict in want_leaf_index.items(): + hdict = have_leaf_index.get(sig, {}) + self.compare( + parsers=vrrp_parsers, + want={"vrrp": wdict}, + have={"vrrp": hdict}, + ) + + def _vrrp_leaf_sig(self, item): + """Build a hashable signature for a VRRP leaf dict for O(1) indexing.""" + 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) + + def _list_to_named_dict(self, data): + """Convert all named-object lists to name-keyed dicts in-place. + + Replaces the three separate _vrrp_groups_list_to_dict, + _vrrp_sync_groups_list_to_dict, and _virtual_servers_list_to_dict + methods with a single helper. Also normalises real_server lists + inside virtual servers. + """ + # VRRP groups and sync_groups + vrrp = data.get("vrrp", {}) + for key in ("groups", "sync_groups"): + items = vrrp.get(key) + if isinstance(items, list): + vrrp[key] = { + item["name"]: item + for item in items + if isinstance(item, dict) and item.get("name") + } + + # Virtual servers + vss = data.get("virtual_servers") + 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 + elif isinstance(vss, dict): + for vs in vss.values(): + if not isinstance(vs, dict): + 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") + } + + 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 _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): + """Strip None and False from config dicts, but preserve "disabled". + + False is the argspec default for boolean flags (disable, no_preempt, + rfc3768_compatibility) and carries no config intent — stripping it + prevents spurious `delete` commands for fields already at their + default state. + + "disabled" is an explicit user choice for snmp and must be preserved + so that _prune_stubs can act on it. The original code stripped it, + which made `snmp: disabled` invisible to the deleted-state logic. + """ + if isinstance(data, dict): + cleaned = {} + for k, v in data.items(): + if v is None or v is False: + 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/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 24c444dd..7f7bad8a 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -30,6 +30,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import ( Firewall_rulesFacts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ha.ha import ( + HaFacts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import ( HostnameFacts, ) @@ -94,6 +97,7 @@ FACT_RESOURCE_SUBSETS = dict( firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, + ha=HaFacts, ospfv3=Ospfv3Facts, ospfv2=Ospfv2Facts, ospf_interfaces=Ospf_interfacesFacts, diff --git a/plugins/module_utils/network/vyos/facts/ha/__init__.py b/plugins/module_utils/network/vyos/facts/ha/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ha/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/ha/ha.py b/plugins/module_utils/network/vyos/facts/ha/ha.py new file mode 100644 index 00000000..fc559783 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ha/ha.py @@ -0,0 +1,174 @@ +# -*- 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): + """Classify config lines into per-object buckets for isolated parsing. + + Each bucket is parsed by a single HaTemplate instance so that facts + from different objects (groups, sync-groups, virtual-servers) never + bleed into each other. + + Keys are namespaced to avoid collisions between a VRRP group and a + sync-group that share the same name (e.g. both named "g1"). + An elif chain ensures each line lands in exactly one bucket. + """ + config_dict = {} + for config_line in data.splitlines(): + vrrp_disable = re.search(r"set high-availability disable", config_line) + vrrp_snmp = re.search(r"set high-availability vrrp snmp", config_line) + vrrp_gp = re.search( + r"set high-availability vrrp global-parameters (\S+).*", + config_line, + ) + vrrp_grp = re.search(r"set high-availability vrrp group (\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) + + if vrrp_disable: + config_dict.setdefault("disable", []).append(config_line) + elif vrrp_snmp: + config_dict.setdefault("vrrp", []).append(config_line) + elif vrrp_gp: + config_dict.setdefault("global_parameters", []).append(config_line) + elif vrrp_grp: + key = "vrrp_group_{0}".format(vrrp_grp.group(1)) + config_dict.setdefault(key, []).append(config_line) + elif vrrp_sg: + key = "vrrp_sg_{0}".format(vrrp_sg.group(1)) + config_dict.setdefault(key, []).append(config_line) + elif vrrp_vsrv: + config_dict.setdefault(vrrp_vsrv.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("ha", 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", {}) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def normalize_config(self, config): + if not config: + return config + + if isinstance(config.get("virtual_servers"), dict): + config["virtual_servers"] = list(config["virtual_servers"].values()) + + 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()) + + 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 new file mode 100644 index 00000000..300b14a7 --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/ha.py @@ -0,0 +1,1011 @@ +# -*- 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(?P<disable>disable) + $""", + 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<name>\S+) + (?:\s+address\s+(?P<address>\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<name>\S+) + (?:\s+algorithm\s+(?P<algorithm>\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<name>\S+) + (?:\s+delay-loop\s+(?P<delay_loop>\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<name>\S+) + (?:\s+forward-method\s+(?P<forward_method>\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<name>\S+) + (?:\s+fwmark\s+(?P<fwmark>\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<name>\S+) + (?:\s+persistence-timeout\s+(?P<persistence_timeout>\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<name>\S+) + (?:\s+port\s+(?P<port>\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<name>\S+) + (?:\s+protocol\s+(?P<protocol>\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<name>\S+) + \sreal-server + \s+(?P<address>\S+) + (?:\s+port\s+(?P<port>\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<name>\S+) + \sreal-server + \s+(?P<address>\S+) + (?:\s+health-check\sscript\s+(?P<hcscript>\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<name>\S+) + \sreal-server + \s+(?P<address>\S+) + (?:\s+connection-timeout\s+(?P<cont>\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<sgname>\S+) + \smember + \s+(?P<member>\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<sgname>\S+) + \shealth-check + (?:\s+failure-count\s+(?P<failure_count>\S+))? + (?:\s+interval\s+(?P<int>\S+))? + (?:\s+ping\s+(?P<ping>\S+))? + (?:\s+script\s+(?P<script>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_sgroup_hc, + "result": { + "vrrp": { + "sync_groups": { + "{{ sgname }}": { + "name": "{{ sgname }}", + "health_check": { + "failure_count": "{{ failure_count if failure_count is defined else None }}", + "interval": "{{ int if int is defined else None }}", + "ping": "{{ ping if ping is defined else None }}", + "script": "{{ script if script is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.sync_groups.transition_script", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\ssync-group + \s+(?P<sgname>\S+) + \stransition-script + (?:\s+backup\s+(?P<backup>\S+))? + (?:\s+fault\s+(?P<fault>\S+))? + (?:\s+master\s+(?P<master>\S+))? + (?:\s+stop\s+(?P<stop>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_sgroup_ts, + "result": { + "vrrp": { + "sync_groups": { + "{{ sgname }}": { + "name": "{{ sgname }}", + "transition_script": { + "backup": "{{ backup if backup is defined else None }}", + "fault": "{{ fault if fault is defined else None }}", + "master": "{{ master if master is defined else None }}", + "stop": "{{ stop if stop is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.global_parameters.garp", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sglobal-parameters + \s+garp + (?:\s+interval\s+(?P<interval>\S+))? + (?:\s+master-delay\s+(?P<master_delay>\S+))? + (?:\s+master-refresh\s+(?P<master_refresh>\S+))? + (?:\s+master-refresh-repeat\s+(?P<master_refresh_repeat>\S+))? + (?:\s+master-repeat\s+(?P<master_repeat>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_gp_garp, + "result": { + "vrrp": { + "global_parameters": { + "garp": { + "interval": "{{ interval if interval is defined else None }}", + "master_delay": "{{ master_delay if master_delay is defined else None }}", + "master_refresh": "{{ master_refresh if master_refresh is defined else None }}", + "master_refresh_repeat": "{{ master_refresh_repeat if master_refresh_repeat is defined else None }}", + "master_repeat": "{{ master_repeat if master_repeat is defined else None }}", + }, + }, + }, + }, + }, + { + "name": "vrrp.global_parameters", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sglobal-parameters + (?=\s+(?:startup-delay|version)\s) + (?:\s+startup-delay\s+(?P<startup_delay>\S+))? + (?:\s+version\s+(?P<version>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_gp, + "result": { + "vrrp": { + "global_parameters": { + "startup_delay": "{{ startup_delay if startup_delay is defined else None }}", + "version": "{{ version if version is defined else None }}", + }, + }, + }, + }, + { + "name": "vrrp.groups", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + (?:\s+description\s+(?P<description>'.+?'|\S+))? + (?:\s+advertise-interval\s+(?P<advertise_interval>\S+))? + (?:\s+hello-source-address\s+(?P<hello_source>\S+))? + (?:\s+interface\s+(?P<interface>\S+))? + (?:\s+peer-address\s+(?P<peer_address>\S+))? + (?:\s+preempt-delay\s+(?P<preempt_delay>\S+))? + (?:\s+priority\s+(?P<priority>\S+))? + (?:\s+vrid\s+(?P<vrid>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "description": "{{ description | replace(\"'\", \"\") if description is defined else None }}", + "advertise_interval": "{{ advertise_interval if advertise_interval is defined else None }}", + "hello_source_address": "{{ hello_source if hello_source is defined else None }}", + "interface": "{{ interface if interface is defined else None }}", + "peer_address": "{{ peer_address if peer_address is defined else None }}", + "preempt_delay": "{{ preempt_delay if preempt_delay is defined else None }}", + "priority": "{{ priority if priority is defined else None }}", + "vrid": "{{ vrid if vrid is defined else None }}", + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.excluded_address", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \sexcluded-address + \s+(?P<excluded_address>.*) + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_exaddress, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "excluded_address": [ + "{{ excluded_address | replace(\"'\", \"\") }}", + ], + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.address", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \saddress + \s+(?P<address>.*) + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_address, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "address": [ + "{{ address | replace(\"'\", \"\") }}", + ], + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.garp", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \s+garp + (?:\s+interval\s+(?P<interval>\S+))? + (?:\s+master-delay\s+(?P<master_delay>\S+))? + (?:\s+master-refresh\s+(?P<master_refresh>\S+))? + (?:\s+master-refresh-repeat\s+(?P<master_refresh_repeat>\S+))? + (?:\s+master-repeat\s+(?P<master_repeat>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_garp, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "garp": { + "interval": "{{ interval if interval is defined else None }}", + "master_delay": "{{ master_delay if master_delay is defined else None }}", + "master_refresh": "{{ master_refresh if master_refresh is defined else None }}", + "master_refresh_repeat": "{{ master_refresh_repeat if master_refresh_repeat is defined else None }}", + "master_repeat": "{{ master_repeat if master_repeat is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.authentication", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \s+authentication + (?:\s+password\s+(?P<password>\S+))? + (?:\s+type\s+(?P<type>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_auth, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "authentication": { + "password": "{{ password if password is defined else None }}", + "type": "{{ type if type is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.transition_script", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \stransition-script + (?:\s+backup\s+(?P<backup>\S+))? + (?:\s+fault\s+(?P<fault>\S+))? + (?:\s+master\s+(?P<master>\S+))? + (?:\s+stop\s+(?P<stop>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_ts, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "transition_script": { + "backup": "{{ backup if backup is defined else None }}", + "fault": "{{ fault if fault is defined else None }}", + "master": "{{ master if master is defined else None }}", + "stop": "{{ stop if stop is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.health_check", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \shealth-check + (?:\s+failure-count\s+(?P<failure_count>\S+))? + (?:\s+interval\s+(?P<int>\S+))? + (?:\s+ping\s+(?P<ping>\S+))? + (?:\s+script\s+(?P<script>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_hc, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "health_check": { + "failure_count": "{{ failure_count if failure_count is defined else None }}", + "interval": "{{ int if int is defined else None }}", + "ping": "{{ ping if ping is defined else None }}", + "script": "{{ script if script is defined else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.track.interface", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \strack + (?:\s+interface\s+(?P<interface>\S+))? + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_track_list, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "track": { + "interface": "{{ [interface.strip(\"'\")] if interface is defined else [] }}", + }, + }, + }, + }, + }, + }, + { + "name": "vrrp.snmp", + "getval": re.compile( + r""" + ^set + \shigh-availability + \svrrp + \s(?P<snmp>snmp) + $""", + re.VERBOSE, + ), + "setval": "high-availability vrrp snmp", + "result": { + "vrrp": { + "snmp": "{{ 'enabled' if snmp is defined else 'disabled' }}", + }, + }, + }, + { + "name": "vrrp.groups.disable", + "getval": re.compile( + r""" + ^set + \shigh-availability\svrrp\sgroup + \s(?P<gname>\S+) + \s(?P<disable>disable) + $""", + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_bool, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "disable": "{{ True if disable is defined else False }}", + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.no_preempt", + "getval": re.compile( + r""" + ^set + \shigh-availability\svrrp\sgroup + \s(?P<gname>\S+) + \s(?P<no_preempt>no-preempt) + $""", + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_bool, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "no_preempt": "{{ True if no_preempt is defined else False }}", + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.rfc3768_compatibility", + "getval": re.compile( + r""" + ^set + \shigh-availability\svrrp\sgroup + \s(?P<gname>\S+) + \s(?P<rfc3768_compatibility>rfc3768-compatibility) + $""", + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_bool, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "rfc3768_compatibility": "{{ True if rfc3768_compatibility is defined else False }}", + }, + }, + }, + }, + }, + { + "name": "vrrp.groups.track.exclude_vrrp_interface", + "getval": re.compile( + r""" + ^set\shigh-availability\svrrp\sgroup + \s+(?P<gname>\S+) + \strack + \s(?P<exclude_vrrp_inter>exclude-vrrp-interface) + $ + """, + re.VERBOSE, + ), + "setval": _tmplt_vrrp_group_track_bool, + "result": { + "vrrp": { + "groups": { + "{{ gname }}": { + "name": "{{ gname }}", + "track": { + "exclude_vrrp_interface": "{{ True if exclude_vrrp_inter is defined else False }}", + }, + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 78445c02..df0a72ca 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -288,12 +288,29 @@ def in_target_not_none(h, key): 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) + """Merge dict ``b`` into dict ``a``, returning a new dict. + + :param a: Base dictionary. + :param b: Dictionary whose values take precedence over ``a``. + :param recursive: When True, nested dicts are merged recursively rather + than replaced wholesale. + :param list_merge: Controls how list values are combined when the same key + exists in both dicts. Supported modes: + + - ``"replace"`` *(default)* — ``b``'s list replaces ``a``'s list. + - ``"append"`` — ``b``'s list is appended to ``a``'s list (duplicates + kept). + - ``"prepend"`` — ``b``'s list is prepended to ``a``'s list + (duplicates kept). + - ``"append_rp"`` — like ``"append"`` but duplicates are removed, + preserving the first occurrence (rp = remove-preserve). + - ``"prepend_rp"`` — like ``"prepend"`` but duplicates are removed, + preserving the first occurrence. + + Passing any other value raises ``ValueError``. + :returns: New merged dict. + :raises ValueError: If either argument is not a dict, or if an + unsupported ``list_merge`` mode is given. """ if not isinstance(a, dict) or not isinstance(b, dict): raise ValueError("combine expects two dictionaries") @@ -301,8 +318,29 @@ def combine(a, b, recursive=False, list_merge="replace"): 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) + if k in result: + # dict merge + if recursive and isinstance(result[k], dict) and isinstance(v, dict): + result[k] = combine(result[k], v, recursive=True, list_merge=list_merge) + + # list merge + elif isinstance(result[k], list) and isinstance(v, list): + if list_merge == "replace": + result[k] = v + elif list_merge == "append": + result[k] = result[k] + v + elif list_merge == "prepend": + result[k] = v + result[k] + elif list_merge == "append_rp": + result[k] = list(dict.fromkeys(result[k] + v)) + elif list_merge == "prepend_rp": + result[k] = list(dict.fromkeys(v + result[k])) + else: + raise ValueError(f"Unsupported list_merge mode: {list_merge}") + + # everything else + else: + result[k] = v else: result[k] = v diff --git a/plugins/modules/vyos_ha.py b/plugins/modules/vyos_ha.py new file mode 100644 index 00000000..ddd02b48 --- /dev/null +++ b/plugins/modules/vyos_ha.py @@ -0,0 +1,1327 @@ +#!/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_ha module, which manages VRRP and load balancer configuration on VyOS +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: vyos_ha +author: Evgeny Molotkov (@omnom62) +short_description: Manage VRRP and load balancer configuration on VyOS +version_added: "1.0.0" +description: + - 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. + +options: + config: + description: + - Full VRRP and virtual server configuration. + type: dict + suboptions: + disable: + description: + - Disable all VRRP and L4-LB configuration under this module. + type: bool + default: false + virtual_servers: + description: + - List of load balancer virtual server (LVS) definitions. + type: list + elements: dict + suboptions: + name: + description: + - Unique identifier for the virtual server. + type: str + required: true + address: + description: + - Virtual IP address for the server. + type: str + algorithm: + description: + - Load balancing algorithm used for dispatching connections. + type: str + delay_loop: + description: + - Delay loop interval in seconds. + type: int + forward_method: + description: + - Forwarding method used by LVS. + type: str + choices: [direct, nat] + fwmark: + description: + - Firewall mark for LVS traffic classification. + type: int + persistence_timeout: + description: + - Client persistence timeout in seconds. + type: int + port: + description: + - TCP/UDP port provided by the virtual service. + type: int + protocol: + description: + - Transport protocol for the virtual server. + type: str + choices: [tcp, udp] + + real_server: + description: + - Backend real servers behind the virtual service. + type: list + elements: dict + suboptions: + address: + description: + - Real server IP address. + type: str + required: true + port: + description: + - Backend server port. + type: int + connection_timeout: + description: + - Backend server connection timeout. + type: int + health_check_script: + description: + - Path to health check script used for backend validation. + type: str + + vrrp: + description: + - VRRP configuration including groups, global parameters, SNMP settings, + and sync-groups. + type: dict + suboptions: + + global_parameters: + description: + - Global VRRP tuning parameters. + type: dict + suboptions: + garp: + description: + - Gratuitous ARP related configuration. + type: dict + suboptions: + interval: + description: + - GARP interval in seconds. + type: int + master_delay: + description: + - Delay before sending GARP as master. + type: int + master_refresh: + description: + - Refresh interval for master GARP announcements. + type: int + master_refresh_repeat: + description: + - Number of times to repeat refresh announcements. + type: int + master_repeat: + description: + - Number of GARP repeats when transitioning to master. + type: int + + startup_delay: + description: + - Delay before VRRP starts after boot. + type: int + + version: + description: + - VRRP protocol version. + type: str + + groups: + description: + - VRRP instance configuration groups. + type: list + elements: dict + suboptions: + name: + description: + - VRRP group name. + type: str + required: true + address: + description: + - Virtual router IP addresses. + type: list + elements: str + + advertise_interval: + description: + - VRRP advertisement interval. + type: int + + authentication: + description: + - VRRP group authentication options. + type: dict + suboptions: + password: + description: + - Authentication password. + type: str + type: + description: + - Authentication type. + type: str + + description: + description: + - Text description for the VRRP group. + type: str + + disable: + description: + - Disable this VRRP group. + type: bool + default: false + + excluded_address: + description: + - IP address excluded from source checks. + type: list + elements: str + + garp: + description: + - GARP-specific settings for this group. + type: dict + suboptions: + interval: + description: GARP interval. + type: int + master_delay: + description: GARP master delay. + type: int + master_refresh: + description: GARP master refresh interval. + type: int + master_refresh_repeat: + description: Repeated refresh sends. + type: int + master_repeat: + description: GARP repeat count. + type: int + + health_check: + description: + - VRRP group health check options. + type: dict + suboptions: + failure_count: + description: Allowed number of failed checks. + type: int + interval: + description: Health check interval. + type: int + ping: + description: Host to ping for checks. + type: str + script: + description: Script to execute for health checking. + type: str + + hello_source_address: + description: + - Source address for VRRP hello packets. + type: str + + interface: + description: + - Interface used by the VRRP group. + type: str + + no_preempt: + description: + - Disable preemption. + type: bool + default: false + + peer_address: + description: + - Peer VRRP router address. + type: str + + preempt_delay: + description: + - Delay before taking master role. + type: int + + priority: + description: + - VRRP priority (higher = preferred master). + type: int + + rfc3768_compatibility: + description: + - Enable or disable RFC3768 compatibility mode. + type: bool + default: false + + track: + description: + - Track interface and VRRP behaviour. + type: dict + suboptions: + exclude_vrrp_interface: + description: + - Exclude VRRP interface from tracking. + type: bool + interface: + description: + - Interface to track. + type: list + elements: str + + transition_script: + description: + - Scripts executed during VRRP state transitions. + type: dict + suboptions: + backup: + description: Path to backup script. + type: str + fault: + description: Path to fault script. + type: str + master: + description: Path to master script. + type: str + stop: + description: Path to stop script. + type: str + + vrid: + description: + - VRRP Virtual Router ID. + type: int + + snmp: + description: + - Enable SNMP support for VRRP. + type: str + choices: ['enabled', 'disabled'] + + sync_groups: + description: + - VRRP sync-groups for coordinated failover. + type: list + elements: dict + suboptions: + name: + description: + - Sync-group name. + type: str + required: true + + health_check: + description: + - Health check options for sync group. + type: dict + suboptions: + failure_count: + description: Allowed number of failures. + type: int + interval: + description: Health check interval. + type: int + ping: + description: Host to ping. + type: str + script: + description: Script to run for health checking. + type: str + + member: + description: + - List of VRRP groups participating in this sync group. + type: list + elements: str + + transition_script: + description: + - Transition scripts for sync group events. + type: dict + suboptions: + backup: + description: Backup state script. + type: str + fault: + description: Fault state script. + type: str + master: + description: Master state script. + type: str + stop: + description: Stop state script. + type: str + + state: + description: + - Desired end state of the VRRP configuration. + type: str + choices: + - deleted + - merged + - purged + - replaced + - gathered + - rendered + - parsed + - overridden + default: merged + + running_config: + description: + - Used only when C(state=parsed). Must contain the output of + C(show configuration commands | grep high-availability). + type: str +""" + +EXAMPLES = """ +# 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 = """ +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 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' +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 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' +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.ha.ha import ( + HaArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ha.ha import ( + Ha, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=HaArgs.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 = Ha(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/vyos_ha/defaults/main.yaml b/tests/integration/targets/vyos_ha/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_ha/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_ha/tasks/cli.yaml b/tests/integration/targets/vyos_ha/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_ha/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_ha/tasks/main.yaml b/tests/integration/targets/vyos_ha/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_ha/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_ha/tests/cli/_add_dummy_scripts.yaml b/tests/integration/targets/vyos_ha/tests/cli/_add_dummy_scripts.yaml new file mode 100644 index 00000000..60a44758 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_add_dummy_scripts.yaml @@ -0,0 +1,6 @@ +--- +- name: Create dummy scripts for HA tests + vyos.vyos.vyos_command: + commands: touch /var/tmp/script.sh && chmod +x /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg new file mode 100644 index 00000000..d003d106 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_parsed.cfg @@ -0,0 +1,69 @@ +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +set high-availability vrrp global-parameters startup-delay 31 +set high-availability vrrp global-parameters version 3 +set high-availability vrrp global-parameters garp interval 30 +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 +set high-availability vrrp global-parameters garp master-repeat 5 +set high-availability vrrp group g1 address 1.1.1.1 +set high-availability vrrp group g1 address 5.5.5.5 +set high-availability vrrp group g1 address 8.8.8.8 interface eth2 +set high-availability vrrp group g1 advertise-interval 10 +set high-availability vrrp group g1 description 'Group_1' +set high-availability vrrp group g1 disable +set high-availability vrrp group g1 interface eth2 +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 preempt-delay 15 +set high-availability vrrp group g1 priority 100 +set high-availability vrrp group g1 rfc3768-compatibility +set high-availability vrrp group g1 vrid 20 +set high-availability vrrp group g1 authentication password testpass +set high-availability vrrp group g1 authentication type plaintext-password +set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 +set high-availability vrrp group g1 excluded-address 192.168.1.8 +set high-availability vrrp group g1 garp interval 20 +set high-availability vrrp group g1 garp master-delay 5 +set high-availability vrrp group g1 garp master-refresh 50 +set high-availability vrrp group g1 garp master-refresh-repeat 100 +set high-availability vrrp group g1 garp master-repeat 3 +set high-availability vrrp group g1 track exclude-vrrp-interface +set high-availability vrrp group g1 track interface eth0 +set high-availability vrrp group g1 track interface eth1 +set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh +set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh +set high-availability vrrp group g1 transition-script master /var/tmp/script.sh +set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh +set high-availability vrrp group g2 address 2.2.2.2 +set high-availability vrrp group g2 description 'Group_2' +set high-availability vrrp group g2 interface eth1 +set high-availability vrrp group g2 vrid 11 +set high-availability vrrp group g2 health-check failure-count 5 +set high-availability vrrp group g2 health-check interval 15 +set high-availability vrrp group g2 health-check ping 192.168.1.100 +set high-availability vrrp group g2 health-check script /var/tmp/script.sh +set high-availability vrrp group g2 hello-source-address 2.2.2.2 +set high-availability vrrp sync-group sg1 health-check failure-count 3 +set high-availability vrrp sync-group sg1 health-check interval 10 +set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 +set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh +set high-availability vrrp sync-group sg1 member g1 +set high-availability vrrp sync-group sg1 member g2 +set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh diff --git a/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml new file mode 100644 index 00000000..95e81752 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_populate_config.yaml @@ -0,0 +1,8 @@ +--- +- ansible.builtin.include_tasks: _add_dummy_scripts.yaml + +- name: Setup initial HA configuration + vyos.vyos.vyos_config: + lines: "{{ populate_commands}}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..416e5c4d --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/_remove_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Remove pre-existing HA config + vyos.vyos.vyos_ha: + config: + state: purged + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli + +- name: Remove dummy scripts for HA tests + vyos.vyos.vyos_command: + commands: + - rm -f /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml new file mode 100644 index 00000000..f97e8802 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_all.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: Start vyos_ha purged / deleted_all integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Purge all the HA configuration. + register: result + vyos.vyos.vyos_ha: &id001 + config: + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ purged.commands | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "purged.after == result['after']" + + - name: Purge all the HA configuration. (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "purged.after == result['before']" + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml new file mode 100644 index 00000000..52a6477b --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_bulk.yaml @@ -0,0 +1,50 @@ +--- +- debug: + msg: Start vyos_ha deleted integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete stub objects. + register: result + vyos.vyos.vyos_ha: &id001 + config: + disable: false + vrrp: + global_parameters: + startup_delay: 32 + garp: {} + groups: + - name: g1 + sync_groups: [] + virtual_servers: [] + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + # - "{{ merged.before | symmetric_difference(result['before']) | length == 0 }}" + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_bulk['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_bulk['after'] | symmetric_difference(result['after']) | length == 0 }}" + + - name: Delete stub objects (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml b/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml new file mode 100644 index 00000000..b84b6905 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/deleted_vrrp_groups.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: Start vyos_ha deleted integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete a vrrp group and sync_group based on its name. + register: result + vyos.vyos.vyos_ha: &id001 + config: + vrrp: + groups: + - name: "g1" + sync_groups: + - name: "sg1" + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_vrrp_groups['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "deleted_vrrp_groups['after'] == result['after']" + + - name: Delete attributes of group (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml new file mode 100644 index 00000000..676d2c66 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_ha 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_ha: + 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_ha: + 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_ha: + 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_ha: + 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_ha: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml b/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml new file mode 100644 index 00000000..da39727a --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/gathered.yaml @@ -0,0 +1,25 @@ +--- +- debug: + msg: START vyos_ha gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_ha: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - name: Assert that facts are correctly generated + assert: + that: + - result.changed == false + - "result.gathered == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/merged.yaml b/tests/integration/targets/vyos_ha/tests/cli/merged.yaml new file mode 100644 index 00000000..cb870d01 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_ha merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - result.changed == true + - result.commands | symmetric_difference(merged.commands) == [] + - "result.after == ansible_facts['network_resources']['ha']" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "merged.before == result.before" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + - "result['before'] == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml b/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml new file mode 100644 index 00000000..44a85b76 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/overridden.yaml @@ -0,0 +1,35 @@ +--- +- debug: + msg: START vyos_ha overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Override the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ overridden.config }}" + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - name: Verify that the configuration was correctly overridden + assert: + that: + - result.changed == true + - result.after == ansible_facts['network_resources']['ha'] + + - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *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_ha/tests/cli/parsed.yaml b/tests/integration/targets/vyos_ha/tests/cli/parsed.yaml new file mode 100644 index 00000000..c7134d6c --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/parsed.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ha parsed integration tests on connection={{ ansible_connection }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_ha: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- name: Assert that config was correctly parsed + assert: + that: + - result.changed == false + - "result.parsed == merged.before" diff --git a/tests/integration/targets/vyos_ha/tests/cli/purged.yaml b/tests/integration/targets/vyos_ha/tests/cli/purged.yaml new file mode 100644 index 00000000..ffc00d64 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/purged.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: Start vyos_ha purged / deleted_all integration tests ansible_connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Purge all the HA configuration. + register: result + vyos.vyos.vyos_ha: &id001 + config: + state: purged + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "merged.before == result['before']" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ purged.commands | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "purged.after == result['after']" + + - name: Purge all the HA configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "purged.after == result['before']" + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml b/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml new file mode 100644 index 00000000..efece2c6 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/rendered.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ha rendered integration tests on connection={{ ansible_connection }} + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_ha: + config: "{{ rendered.config }}" + state: rendered + + - assert: + that: + - result.changed == false + - "result.rendered | sort == rendered.commands | sort" diff --git a/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml b/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml new file mode 100644 index 00000000..d35f3345 --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/replaced.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_ha replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ha: &id001 + config: "{{ merged.config }}" + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - result.changed == true + - result.commands | symmetric_difference(replaced.commands) == [] + - "result.after == ansible_facts['network_resources']['ha']" + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ha: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + - "result['before'] == ansible_facts['network_resources']['ha']" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml b/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml new file mode 100644 index 00000000..06e017be --- /dev/null +++ b/tests/integration/targets/vyos_ha/tests/cli/rtt.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: START vyos_ha rtt integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: RTT Merge the provided configuration with the existing running configuration + register: baseconfig + vyos.vyos.vyos_ha: + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: ha + + - assert: + that: + - baseconfig.changed == true + - baseconfig.commands|symmetric_difference(rtt.commands) == [] + - "baseconfig.after == ansible_facts['network_resources']['ha']" + + - name: RTT Merge the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_ha: + config: "{{ rtt.config }}" + + - name: RTT Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_ha: + config: "{{ ansible_facts['network_resources']['ha'] }}" + state: overridden + + - name: RTT Assert that config was reverted + assert: + that: baseconfig.after == revert.after + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ha/vars/main.yaml b/tests/integration/targets/vyos_ha/vars/main.yaml new file mode 100644 index 00000000..6cf70f2e --- /dev/null +++ b/tests/integration/targets/vyos_ha/vars/main.yaml @@ -0,0 +1,787 @@ +--- +merged: + before: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: "round-robin" + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + health_check_script: "/var/tmp/script.sh" + port: 443 + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "5.5.5.5" + - "8.8.8.8 interface eth2" + advertise_interval: 10 + peer_address: 192.168.1.3 + preempt_delay: 15 + priority: 100 + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + excluded_address: + - "192.168.1.7 interface eth3" + - "192.168.1.8" + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + interface: + - eth0 + - eth1 + - name: "g2" + description: "Group_2" + interface: "eth1" + address: + - "2.2.2.2" + disable: false + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + hello_source_address: "2.2.2.2" + sync_groups: + - name: "sg1" + member: + - "g1" + - "g2" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 3 + interval: 10 + ping: "192.168.2.100" + script: "/var/tmp/script.sh" + 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 + - name: "g2" + address: + - "4.4.4.4" + - "6.6.6.6 interface eth3" + sync_groups: + - name: "sg1" + health_check: + failure_count: 5 + commands: + - delete high-availability vrrp group g1 disable + - delete high-availability vrrp group g1 no-preempt + - delete high-availability vrrp group g1 rfc3768-compatibility + - set high-availability disable + - set high-availability virtual-server s1 address 10.10.10.5 + - set high-availability virtual-server s1 real-server 10.10.50.2 port 8443 + - set high-availability virtual-server s2 persistence-timeout 30 + - 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 g2 address 2.2.2.2 + - set high-availability vrrp group g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + +populate_commands: + - set high-availability virtual-server s1 address 10.10.10.1 + - set high-availability virtual-server s1 algorithm round-robin + - set high-availability virtual-server s1 delay-loop 60 + - set high-availability virtual-server s1 forward-method direct + - set high-availability virtual-server s1 fwmark 10 + - set high-availability virtual-server s1 persistence-timeout 30 + - set high-availability virtual-server s1 protocol tcp + - set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh + - set high-availability virtual-server s1 real-server 10.10.50.1 port 443 + - set high-availability virtual-server s2 address 10.10.10.2 + - set high-availability virtual-server s2 port 81 + - set high-availability virtual-server s2 real-server real1 connection-timeout 5 + - set high-availability virtual-server s2 real-server real1 port 8081 + - set high-availability virtual-server s2 real-server real2 port 8080 + - set high-availability vrrp global-parameters startup-delay 31 + - set high-availability vrrp global-parameters version 3 + - set high-availability vrrp global-parameters garp interval 30 + - 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 + - set high-availability vrrp global-parameters garp master-repeat 5 + - set high-availability vrrp group g1 address 1.1.1.1 + - set high-availability vrrp group g1 address 5.5.5.5 + - set high-availability vrrp group g1 address 8.8.8.8 interface eth2 + - set high-availability vrrp group g1 advertise-interval 10 + - set high-availability vrrp group g1 description 'Group_1' + - set high-availability vrrp group g1 disable + - set high-availability vrrp group g1 interface eth2 + - 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 preempt-delay 15 + - set high-availability vrrp group g1 priority 100 + - set high-availability vrrp group g1 rfc3768-compatibility + - set high-availability vrrp group g1 vrid 20 + - set high-availability vrrp group g1 authentication password testpass + - set high-availability vrrp group g1 authentication type plaintext-password + - set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 + - set high-availability vrrp group g1 excluded-address 192.168.1.8 + - set high-availability vrrp group g1 garp interval 20 + - set high-availability vrrp group g1 garp master-delay 5 + - set high-availability vrrp group g1 garp master-refresh 50 + - set high-availability vrrp group g1 garp master-refresh-repeat 100 + - set high-availability vrrp group g1 garp master-repeat 3 + - set high-availability vrrp group g1 track exclude-vrrp-interface + - set high-availability vrrp group g1 track interface eth0 + - set high-availability vrrp group g1 track interface eth1 + - set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script master /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh + - set high-availability vrrp group g2 address 2.2.2.2 + - set high-availability vrrp group g2 description 'Group_2' + - set high-availability vrrp group g2 interface eth1 + - set high-availability vrrp group g2 vrid 11 + - set high-availability vrrp group g2 health-check failure-count 5 + - set high-availability vrrp group g2 health-check interval 15 + - set high-availability vrrp group g2 health-check ping 192.168.1.100 + - set high-availability vrrp group g2 health-check script /var/tmp/script.sh + - set high-availability vrrp group g2 hello-source-address 2.2.2.2 + - set high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability vrrp sync-group sg1 health-check interval 10 + - set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 + - set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 member g1 + - set high-availability vrrp sync-group sg1 member g2 + - set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh + +rendered: + config: + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + health_check_script: /var/tmp/script.sh + port: 443 + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + snmp: enabled + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "2.2.2.2" + - "3.3.3.3 interface eth2" + advertise_interval: 10 + peer_address: 192.168.1.3 + preempt_delay: 15 + priority: 100 + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + excluded_address: + - "192.168.1.7 interface eth3" + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "test123" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + interface: + - eth0 + - name: "g2" + description: "Group_2" + interface: "eth1" + address: "2.2.2.2" + disable: false + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + hello_source_address: "2.2.2.2" + sync_groups: + - name: "sg1" + member: + - "g1" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 3 + interval: 10 + ping: "192.168.2.100" + script: "/var/tmp/script.sh" + commands: + - set high-availability virtual-server s1 address 10.10.10.1 + - set high-availability virtual-server s1 algorithm round-robin + - set high-availability virtual-server s1 delay-loop 60 + - set high-availability virtual-server s1 forward-method direct + - set high-availability virtual-server s1 fwmark 10 + - set high-availability virtual-server s1 persistence-timeout 30 + - set high-availability virtual-server s1 protocol tcp + - set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh + - set high-availability virtual-server s1 real-server 10.10.50.1 port 443 + - set high-availability virtual-server s2 address 10.10.10.2 + - set high-availability virtual-server s2 port 81 + - set high-availability virtual-server s2 real-server real1 connection-timeout 5 + - set high-availability virtual-server s2 real-server real1 port 8081 + - set high-availability virtual-server s2 real-server real2 port 8080 + - set high-availability vrrp global-parameters garp interval 30 + - 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 + - set high-availability vrrp global-parameters garp master-repeat 5 + - set high-availability vrrp global-parameters startup-delay 31 + - set high-availability vrrp global-parameters version 3 + - set high-availability vrrp group g1 address 1.1.1.1 + - set high-availability vrrp group g1 address 2.2.2.2 + - set high-availability vrrp group g1 address 3.3.3.3 interface eth2 + - set high-availability vrrp group g1 advertise-interval 10 + - set high-availability vrrp group g1 authentication password ******** + - set high-availability vrrp group g1 authentication type plaintext-password + - set high-availability vrrp group g1 description 'Group_1' + - set high-availability vrrp group g1 disable + - set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 + - set high-availability vrrp group g1 garp interval 20 + - set high-availability vrrp group g1 garp master-delay 5 + - set high-availability vrrp group g1 garp master-refresh 50 + - set high-availability vrrp group g1 garp master-refresh-repeat 100 + - set high-availability vrrp group g1 garp master-repeat 3 + - set high-availability vrrp group g1 interface eth2 + - 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 preempt-delay 15 + - set high-availability vrrp group g1 priority 100 + - set high-availability vrrp group g1 rfc3768-compatibility + - set high-availability vrrp group g1 track exclude-vrrp-interface + - set high-availability vrrp group g1 track interface eth0 + - set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script master /var/tmp/script.sh + - set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh + - set high-availability vrrp group g1 vrid 20 + - set high-availability vrrp group g2 address 2.2.2.2 + - set high-availability vrrp group g2 description 'Group_2' + - set high-availability vrrp group g2 health-check failure-count 5 + - set high-availability vrrp group g2 health-check interval 15 + - set high-availability vrrp group g2 health-check ping 192.168.1.100 + - set high-availability vrrp group g2 health-check script /var/tmp/script.sh + - set high-availability vrrp group g2 hello-source-address 2.2.2.2 + - set high-availability vrrp group g2 interface eth1 + - set high-availability vrrp group g2 vrid 11 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability vrrp sync-group sg1 health-check interval 10 + - set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 + - set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 member g1 + - set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh + - set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh + +purged: + commands: + - delete high-availability + after: + disable: false + +deleted_vrrp_groups: + commands: + - delete high-availability vrrp group g1 + - delete high-availability vrrp sync-group sg1 + after: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + port: 443 + health_check_script: /var/tmp/script.sh + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + port: 8081 + connection_timeout: 5 + - address: real2 + port: 8080 + vrrp: + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: g2 + address: + - "2.2.2.2" + description: Group_2 + disable: false + interface: eth1 + vrid: 11 + hello_source_address: 2.2.2.2 + no_preempt: false + rfc3768_compatibility: false + health_check: + failure_count: 5 + interval: 15 + ping: 192.168.1.100 + script: /var/tmp/script.sh + +deleted_bulk: + commands: + - delete high-availability virtual-server + - delete high-availability vrrp global-parameters garp + - delete high-availability vrrp global-parameters startup-delay + - delete high-availability vrrp group g1 + - delete high-availability vrrp sync-group + after: + disable: false + vrrp: + global_parameters: + version: "3" + groups: + - name: g2 + address: + - "2.2.2.2" + description: Group_2 + disable: false + hello_source_address: 2.2.2.2 + interface: eth1 + vrid: 11 + no_preempt: false + rfc3768_compatibility: false + health_check: + failure_count: 5 + interval: 15 + ping: 192.168.1.100 + script: /var/tmp/script.sh + +overridden: + config: + disable: false + vrrp: + snmp: disabled + global_parameters: + startup_delay: 32 + version: 3 + garp: + interval: 30 + master_delay: 11 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 6 + groups: + - name: "g1" + description: "Group_1" + interface: "eth2" + address: + - "1.1.1.1" + - "3.3.3.3 interface eth3" + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + peer_address: "192.168.1.3" + advertise_interval: 10 + priority: 100 + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "test123" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + sync_groups: + - name: "sg1" + member: + - "g1" + - "g2" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 4 + interval: 10 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + virtual_servers: + - name: "s1" + address: "10.10.10.15" + algorithm: "round-robin" + delay_loop: 60 + fwmark: 12 + forward_method: "direct" + persistence_timeout: 30 + protocol: "tcp" + real_server: + - address: "10.10.10.1" + connection_timeout: 61 + port: 443 + after: + disable: false + virtual_servers: + - address: "10.10.10.15" + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 12 + name: s1 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: "10.10.10.1" + connection_timeout: 61 + port: 443 + - address: "10.10.10.2" + name: s2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + global_parameters: + garp: + interval: 30 + master_delay: 11 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 6 + startup_delay: 32 + version: "3" + groups: + - address: "1.1.1.1" + advertise_interval: 10 + authentication: + password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + type: plaintext-password + description: Group_1 + disable: true + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + interface: eth2 + name: g1 + no_preempt: true + peer_address: "192.168.1.3" + priority: 100 + rfc3768_compatibility: true + track: + exclude_vrrp_interface: true + transition_script: + backup: /var/tmp/script.sh + fault: /var/tmp/script.sh + master: /var/tmp/script.sh + stop: /var/tmp/script.sh + vrid: 20 + - address: "2.2.2.2" + description: Group_2 + disable: false + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: /var/tmp/script.sh + hello_source_address: "2.2.2.2" + interface: eth1 + name: g2 + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + sync_groups: + - health_check: + failure_count: 4 + interval: 10 + ping: "192.168.1.100" + script: /var/tmp/script.sh + member: + - g1 + - g2 + name: sg1 + transition_script: + backup: /var/tmp/script.sh + fault: /var/tmp/script.sh + master: /var/tmp/script.sh + stop: /var/tmp/script.sh + +replaced: + commands: + - delete high-availability vrrp global-parameters garp master-repeat 5 + - delete high-availability vrrp global-parameters startup-delay 31 + - delete high-availability vrrp group g1 disable + - delete high-availability vrrp group g1 no-preempt + - delete high-availability vrrp group g1 rfc3768-compatibility + - delete high-availability vrrp group g2 address 2.2.2.2 + - delete high-availability vrrp sync-group sg1 health-check failure-count 3 + - set high-availability disable + - set high-availability virtual-server s1 address 10.10.10.5 + - set high-availability virtual-server s1 real-server 10.10.50.2 port 8443 + - set high-availability virtual-server s2 persistence-timeout 30 + - 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 g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + +rtt: + 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 group g2 address 4.4.4.4 + - set high-availability vrrp group g2 address 6.6.6.6 interface eth3 + - set high-availability vrrp snmp + - set high-availability vrrp sync-group sg1 health-check failure-count 5 + config: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.5 + fwmark: 10 + algorithm: least-connection + real_server: + - address: 10.10.50.2 + port: 8443 + - name: s2 + address: 10.10.10.2 + persistence_timeout: 36 + port: 443 + protocol: tcp + real_server: + - address: 10.10.50.3 + port: 8443 + - name: s3 + address: 10.10.10.3 + port: 80 + protocol: udp + real_server: + - address: 10.10.50.4 + port: 8443 + vrrp: + snmp: disabled + global_parameters: + startup_delay: 36 + garp: + master_repeat: 6 + groups: + - name: "g1" + peer_address: 192.168.1.3 + address: "1.1.1.1" + priority: 100 + disable: true + no_preempt: false + interface: eth2 + vrid: 20 + - name: "g2" + address: + - "2.2.2.2" + interface: eth1 + vrid: 11 + sync_groups: + - name: "sg1" + health_check: + failure_count: 10 + 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" diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.19.txt +++ b/tests/sanity/ignore-2.19.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.20.txt +++ b/tests/sanity/ignore-2.20.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.21.txt b/tests/sanity/ignore-2.21.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.21.txt +++ b/tests/sanity/ignore-2.21.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/sanity/ignore-2.22.txt b/tests/sanity/ignore-2.22.txt index bae35e38..255b0a87 100644 --- a/tests/sanity/ignore-2.22.txt +++ b/tests/sanity/ignore-2.22.txt @@ -8,6 +8,7 @@ plugins/action/facts.py action-plugin-docs plugins/action/firewall_global.py action-plugin-docs plugins/action/firewall_interfaces.py action-plugin-docs plugins/action/firewall_rules.py action-plugin-docs +plugins/action/ha.py action-plugin-docs plugins/action/hostname.py action-plugin-docs plugins/action/interface.py action-plugin-docs plugins/action/interfaces.py action-plugin-docs diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg new file mode 100644 index 00000000..ee69c65d --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_ha_config.cfg @@ -0,0 +1,64 @@ +set high-availability disable +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script '/var/tmp/script.sh' +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +set high-availability vrrp global-parameters garp interval 30 +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 +set high-availability vrrp global-parameters garp master-repeat 5 +set high-availability vrrp global-parameters startup-delay 30 +set high-availability vrrp global-parameters version 3 +set high-availability vrrp group g1 address '1.1.1.1' +set high-availability vrrp group g1 address '3.3.3.3' +set high-availability vrrp group g1 address '5.5.5.5' interface 'eth2' +set high-availability vrrp group g1 advertise-interval 10 +set high-availability vrrp group g1 authentication password 'testpass' +set high-availability vrrp group g1 authentication type 'plaintext-password' +set high-availability vrrp group g1 description 'Group_1' +set high-availability vrrp group g1 disable +set high-availability vrrp group g1 excluded-address '192.168.1.7' interface 'eth3' +set high-availability vrrp group g1 excluded-address '192.168.1.8' +set high-availability vrrp group g1 garp interval 20 +set high-availability vrrp group g1 garp master-delay 5 +set high-availability vrrp group g1 garp master-refresh 50 +set high-availability vrrp group g1 garp master-refresh-repeat 100 +set high-availability vrrp group g1 garp master-repeat 3 +set high-availability vrrp group g1 health-check failure-count 3 +set high-availability vrrp group g1 health-check interval 10 +set high-availability vrrp group g1 health-check ping '192.168.1.5' +set high-availability vrrp group g1 health-check script 'script.sh' +set high-availability vrrp group g1 hello-source-address '192.168.1.2' +set high-availability vrrp group g1 interface 'eth2' +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 priority 100 +set high-availability vrrp group g1 rfc3768-compatibility +set high-availability vrrp group g1 track exclude-vrrp-interface +set high-availability vrrp group g1 track interface 'eth1' +set high-availability vrrp group g1 transition-script backup '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script fault '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script master '/var/tmp/script.sh' +set high-availability vrrp group g1 transition-script stop '/var/tmp/script.sh' +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 +set high-availability vrrp sync-group sg1 health-check interval 10 +set high-availability vrrp sync-group sg1 health-check ping '192.168.1.1' +set high-availability vrrp sync-group sg1 health-check script '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 member 'g1' +set high-availability vrrp sync-group sg1 transition-script backup '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script fault '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script master '/var/tmp/script.sh' +set high-availability vrrp sync-group sg1 transition-script stop '/var/tmp/script.sh' diff --git a/tests/unit/modules/network/vyos/test_vyos_ha.py b/tests/unit/modules/network/vyos/test_vyos_ha.py new file mode 100644 index 00000000..54833a36 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_ha.py @@ -0,0 +1,1405 @@ +# (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 <http://www.gnu.org/licenses/>. + +# 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_ha +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosHaModule(TestVyosModule): + module = vyos_ha + + def setUp(self): + super(TestVyosHaModule, 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.ha.ha.HaFacts.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.ha.ha.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(TestVyosHaModule, 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_ha_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_vrrp_merged_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + master_refresh=100, + ), + version="3", + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_merged(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + name="s1", + address="10.10.10.5", + algorithm="round-robin", + real_server=[ + dict( + address="10.10.50.2", + port=8443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + persistence_timeout=30, + port=81, + protocol="tcp", + ), + dict( + name="s3", + address="10.10.10.3", + port=88, + protocol="udp", + ), + ], + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=35, + garp=dict( + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + peer_address="192.168.1.3", + priority=100, + disable=False, + no_preempt=False, + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + preempt_delay=15, + ), + ], + sync_groups=[ + dict( + name="sg1", + health_check=dict( + failure_count=5, + ), + ), + ], + ), + ), + state="merged", + ), + ) + commands = [ + "delete high-availability vrrp group g1 no-preempt", + "delete high-availability vrrp group g1 disable", + "delete high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "set high-availability vrrp group g2 preempt-delay 15", + "delete high-availability vrrp snmp", + "set high-availability virtual-server s1 address 10.10.10.5", + "set high-availability virtual-server s1 real-server 10.10.50.2 port 8443", + "set high-availability virtual-server s2 persistence-timeout 30", + "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 35", + "set high-availability vrrp sync-group sg1 health-check failure-count 5", + ] + + self.execute_module(changed=True, commands=commands) + + def test_vrrp_replaced(self): + set_module_args( + dict( + config=dict( + disable=False, + virtual_servers=[ + dict( + name="s1", + address="10.10.10.5", + algorithm="round-robin", + real_server=[ + dict( + address="10.10.50.2", + port=8443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + persistence_timeout=30, + port=81, + protocol="tcp", + ), + dict( + name="s3", + address="10.10.10.3", + port=88, + protocol="udp", + ), + ], + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=32, + garp=dict( + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + peer_address="192.168.1.3", + priority=100, + disable=False, + no_preempt=False, + vrid=20, + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + ), + ], + sync_groups=[ + dict( + name="sg1", + health_check=dict( + failure_count=5, + ), + ), + ], + ), + ), + state="replaced", + ), + ) + commands = [ + "delete high-availability vrrp global-parameters garp master-repeat 5", + "delete high-availability vrrp global-parameters startup-delay 30", + "delete high-availability vrrp group g1 disable", + "delete high-availability vrrp group g1 no-preempt", + "delete high-availability vrrp group g1 rfc3768-compatibility", + "delete high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "delete high-availability disable", + "delete high-availability vrrp snmp", + "set high-availability virtual-server s1 address 10.10.10.5", + "set high-availability virtual-server s1 real-server 10.10.50.2 port 8443", + "set high-availability virtual-server s2 persistence-timeout 30", + "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 32", + "set high-availability vrrp sync-group sg1 health-check failure-count 5", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + version="3", + startup_delay=30, + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.1", + script="/var/tmp/script.sh", + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_overridden(self): + set_module_args( + dict( + config=dict( + disable=False, + vrrp=dict( + snmp="disabled", + global_parameters=dict( + startup_delay=32, + version=3, + garp=dict( + interval=30, + master_delay=11, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=6, + ), + ), + groups=[ + dict( + name="g1", + description="Group_1", + interface="eth2", + address="1.1.1.1", + disable=True, + no_preempt=True, + rfc3768_compatibility=True, + vrid=20, + peer_address="192.168.1.3", + advertise_interval=10, + priority=100, + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + authentication=dict( + type="plaintext-password", + password="testpass", + ), + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + track=dict( + exclude_vrrp_interface=True, + ), + ), + dict( + name="g2", + address=[ + "192.168.3.3", + "192.168.4.4 interface eth3", + ], + ), + ], + sync_groups=[ + dict( + name="sg1", + member=["g1", "g2"], + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + health_check=dict( + failure_count=4, + interval=10, + ping="192.168.1.100", + script="/var/tmp/script.sh", + ), + ), + ], + ), + virtual_servers=[ + dict( + name="s1", + address="10.10.10.15", + algorithm="round-robin", + delay_loop=60, + fwmark=12, + forward_method="direct", + persistence_timeout=30, + protocol="tcp", + real_server=[ + dict( + address="10.10.10.1", + connection_timeout=61, + port=443, + ), + ], + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "delete high-availability disable", + "delete high-availability virtual-server s1", + "delete high-availability vrrp global-parameters garp", + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group sg1", + "set high-availability virtual-server s1 address 10.10.10.15", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 12", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.10.1 connection-timeout 61", + "set high-availability virtual-server s1 real-server 10.10.10.1 port 443", + "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 6", + "set high-availability vrrp global-parameters startup-delay 32", + "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 authentication password testpass", + "set high-availability vrrp group g1 authentication type plaintext-password", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 interface eth2", + "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 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address 192.168.3.3", + "set high-availability vrrp group g2 address 192.168.4.4 interface eth3", + "set high-availability vrrp sync-group sg1 health-check failure-count 4", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping 192.168.1.100", + "set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 member g1", + "set high-availability vrrp sync-group sg1 member g2", + "set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + global_parameters=dict( + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + version="3", + startup_delay=30, + ), + groups=[ + dict( + authentication=dict( + password="testpass", + type="plaintext-password", + ), + address=[ + "1.1.1.1", + "3.3.3.3", + "5.5.5.5 interface eth2", + ], + advertise_interval=10, + description="Group_1", + disable=True, + excluded_address=[ + "192.168.1.8", + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.5", + script="script.sh", + ), + hello_source_address="192.168.1.2", + interface="eth2", + name="g1", + no_preempt=True, + peer_address="192.168.1.3", + priority=100, + rfc3768_compatibility=True, + track=dict( + exclude_vrrp_interface=True, + interface=["eth1"], + ), + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + vrid=20, + ), + ], + snmp="enabled", + sync_groups=[ + dict( + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.1.1", + script="/var/tmp/script.sh", + ), + member=["g1"], + name="sg1", + transition_script=dict( + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + master="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vrrp_rendered(self): + set_module_args( + dict( + config=dict( + virtual_servers=[ + dict( + name="s1", + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + persistence_timeout=30, + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + health_check_script="/var/tmp/script.sh", + port=443, + ), + ], + ), + dict( + name="s2", + address="10.10.10.2", + port=81, + real_server=[ + dict( + address="real1", + connection_timeout=5, + port=8081, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + vrrp=dict( + snmp="enabled", + global_parameters=dict( + startup_delay=31, + version="3", + garp=dict( + interval=30, + master_delay=10, + master_refresh=100, + master_refresh_repeat=200, + master_repeat=5, + ), + ), + groups=[ + dict( + name="g1", + description="Group_1", + interface="eth2", + address="1.1.1.1", + advertise_interval=10, + peer_address="192.168.1.3", + priority=100, + disable=True, + no_preempt=True, + rfc3768_compatibility=True, + vrid=20, + excluded_address=[ + "192.168.1.7 interface eth3", + ], + garp=dict( + interval=20, + master_delay=5, + master_refresh=50, + master_refresh_repeat=100, + master_repeat=3, + ), + authentication=dict( + type="plaintext-password", + password="testpass", + ), + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + track=dict( + exclude_vrrp_interface=True, + interface=[ + "eth0", + ], + ), + ), + dict( + name="g2", + description="Group_2", + interface="eth1", + address=[ + "2.2.2.2", + "2.2.2.3 interface eth3", + ], + disable=False, + no_preempt=False, + rfc3768_compatibility=False, + vrid=11, + health_check=dict( + failure_count=5, + interval=15, + ping="192.168.1.100", + script="/var/tmp/script.sh", + ), + hello_source_address="2.2.2.2", + ), + ], + sync_groups=[ + dict( + name="sg1", + member=[ + "g1", + ], + transition_script=dict( + master="/var/tmp/script.sh", + backup="/var/tmp/script.sh", + fault="/var/tmp/script.sh", + stop="/var/tmp/script.sh", + ), + health_check=dict( + failure_count=3, + interval=10, + ping="192.168.2.100", + script="/var/tmp/script.sh", + ), + ), + ], + ), + ), + state="rendered", + ), + ) + rendered_commands = [ + "set high-availability virtual-server s1 address 10.10.10.1", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 10", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh", + "set high-availability virtual-server s1 real-server 10.10.50.1 port 443", + "set high-availability virtual-server s2 address 10.10.10.2", + "set high-availability virtual-server s2 port 81", + "set high-availability virtual-server s2 real-server real1 connection-timeout 5", + "set high-availability virtual-server s2 real-server real1 port 8081", + "set high-availability virtual-server s2 real-server real2 port 8080", + "set high-availability vrrp global-parameters garp interval 30", + "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", + "set high-availability vrrp global-parameters garp master-repeat 5", + "set high-availability vrrp global-parameters startup-delay 31", + "set high-availability vrrp global-parameters version 3", + "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 authentication password testpass", + "set high-availability vrrp group g1 authentication type plaintext-password", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 interface eth2", + "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 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 track interface eth0", + "set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address 2.2.2.2", + "set high-availability vrrp group g2 address 2.2.2.3 interface eth3", + "set high-availability vrrp group g2 description 'Group_2'", + "set high-availability vrrp group g2 health-check failure-count 5", + "set high-availability vrrp group g2 health-check interval 15", + "set high-availability vrrp group g2 health-check ping 192.168.1.100", + "set high-availability vrrp group g2 health-check script /var/tmp/script.sh", + "set high-availability vrrp group g2 hello-source-address 2.2.2.2", + "set high-availability vrrp group g2 interface eth1", + "set high-availability vrrp group g2 vrid 11", + "set high-availability vrrp snmp", + "set high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100", + "set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 member g1", + "set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh", + "set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh", + ] + result = self.execute_module(changed=False) + # rendered output order is not significant — use sorted comparison + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_commands), + result["rendered"], + ) + + def test_vrrp_parsed(self): + commands = ( + "set high-availability disable", + "set high-availability virtual-server s1 address 10.10.10.1", + "set high-availability virtual-server s1 algorithm round-robin", + "set high-availability virtual-server s1 delay-loop 60", + "set high-availability virtual-server s1 forward-method direct", + "set high-availability virtual-server s1 fwmark 10", + "set high-availability virtual-server s1 persistence-timeout 30", + "set high-availability virtual-server s1 protocol tcp", + "set high-availability virtual-server s1 real-server 10.10.50.1 health-check script '/var/tmp/script.sh'", + "set high-availability virtual-server s1 real-server 10.10.50.1 port 443", + "set high-availability virtual-server s2 address 10.10.10.2", + "set high-availability virtual-server s2 port 81", + "set high-availability virtual-server s2 real-server real1 connection-timeout 5", + "set high-availability virtual-server s2 real-server real1 port 8081", + "set high-availability virtual-server s2 real-server real2 port 8080", + "set high-availability vrrp global-parameters garp interval 30", + "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", + "set high-availability vrrp global-parameters garp master-repeat 5", + "set high-availability vrrp global-parameters startup-delay 30", + "set high-availability vrrp global-parameters version 3", + "set high-availability vrrp group g1 address '1.1.1.1'", + "set high-availability vrrp group g1 address '3.3.3.3'", + "set high-availability vrrp group g1 address '5.5.5.5' interface 'eth2'", + "set high-availability vrrp group g1 advertise-interval 10", + "set high-availability vrrp group g1 authentication password 'testpass'", + "set high-availability vrrp group g1 authentication type 'plaintext-password'", + "set high-availability vrrp group g1 description 'Group_1'", + "set high-availability vrrp group g1 disable", + "set high-availability vrrp group g1 excluded-address '192.168.1.7' interface 'eth3'", + "set high-availability vrrp group g1 excluded-address '192.168.1.8'", + "set high-availability vrrp group g1 garp interval 20", + "set high-availability vrrp group g1 garp master-delay 5", + "set high-availability vrrp group g1 garp master-refresh 50", + "set high-availability vrrp group g1 garp master-refresh-repeat 100", + "set high-availability vrrp group g1 garp master-repeat 3", + "set high-availability vrrp group g1 health-check failure-count 3", + "set high-availability vrrp group g1 health-check interval 10", + "set high-availability vrrp group g1 health-check ping '192.168.1.5'", + "set high-availability vrrp group g1 health-check script 'script.sh'", + "set high-availability vrrp group g1 hello-source-address '192.168.1.2'", + "set high-availability vrrp group g1 interface 'eth2'", + "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 priority 100", + "set high-availability vrrp group g1 rfc3768-compatibility", + "set high-availability vrrp group g1 track exclude-vrrp-interface", + "set high-availability vrrp group g1 track interface 'eth1'", + "set high-availability vrrp group g1 transition-script backup '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script fault '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script master '/var/tmp/script.sh'", + "set high-availability vrrp group g1 transition-script stop '/var/tmp/script.sh'", + "set high-availability vrrp group g1 vrid 20", + "set high-availability vrrp group g2 address '192.168.3.3'", + "set high-availability vrrp group g2 address '192.168.4.4' interface 'eth3'", + "set high-availability vrrp snmp", + "set high-availability vrrp sync-group sg1 health-check failure-count 3", + "set high-availability vrrp sync-group sg1 health-check interval 10", + "set high-availability vrrp sync-group sg1 health-check ping '192.168.1.1'", + "set high-availability vrrp sync-group sg1 health-check script '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 member 'g1'", + "set high-availability vrrp sync-group sg1 transition-script backup '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script fault '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script master '/var/tmp/script.sh'", + "set high-availability vrrp sync-group sg1 transition-script stop '/var/tmp/script.sh'", + ) + parsed_str = "\n".join(commands) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = { + "disable": True, + "virtual_servers": [ + { + "name": "s1", + "address": "10.10.10.1", + "algorithm": "round-robin", + "delay_loop": 60, + "forward_method": "direct", + "fwmark": 10, + "persistence_timeout": 30, + "protocol": "tcp", + "real_server": [ + { + "address": "10.10.50.1", + "health_check_script": "/var/tmp/script.sh", + "port": 443, + }, + ], + }, + { + "name": "s2", + "address": "10.10.10.2", + "port": 81, + "real_server": [ + { + "address": "real1", + "connection_timeout": 5, + "port": 8081, + }, + { + "address": "real2", + "port": 8080, + }, + ], + }, + ], + "vrrp": { + "snmp": "enabled", + "global_parameters": { + "startup_delay": 30, + "version": "3", + "garp": { + "interval": 30, + "master_delay": 10, + "master_refresh": 100, + "master_refresh_repeat": 200, + "master_repeat": 5, + }, + }, + "groups": [ + { + "name": "g1", + "description": "Group_1", + "disable": True, + "no_preempt": True, + "rfc3768_compatibility": True, + "interface": "eth2", + "address": ["1.1.1.1", "3.3.3.3", "5.5.5.5 interface eth2"], + "advertise_interval": 10, + "peer_address": "192.168.1.3", + "priority": 100, + "vrid": 20, + "garp": { + "interval": 20, + "master_delay": 5, + "master_refresh": 50, + "master_refresh_repeat": 100, + "master_repeat": 3, + }, + "authentication": { + "type": "plaintext-password", + "password": "testpass", + }, + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.5", + "script": "script.sh", + }, + "track": { + "exclude_vrrp_interface": True, + "interface": ["eth1"], + }, + "excluded_address": [ + "192.168.1.7 interface eth3", + "192.168.1.8", + ], + "hello_source_address": "192.168.1.2", + }, + { + "name": "g2", + "address": ["192.168.3.3", "192.168.4.4 interface eth3"], + "disable": False, + "no_preempt": False, + "rfc3768_compatibility": False, + }, + ], + "sync_groups": [ + { + "name": "sg1", + "member": ["g1"], + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.1", + "script": "/var/tmp/script.sh", + }, + }, + ], + }, + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_vrrp_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = { + "disable": True, + "virtual_servers": [ + { + "name": "s1", + "address": "10.10.10.1", + "algorithm": "round-robin", + "delay_loop": 60, + "forward_method": "direct", + "fwmark": 10, + "persistence_timeout": 30, + "protocol": "tcp", + "real_server": [ + { + "address": "10.10.50.1", + "health_check_script": "/var/tmp/script.sh", + "port": 443, + }, + ], + }, + { + "name": "s2", + "address": "10.10.10.2", + "port": 81, + "real_server": [ + { + "address": "real1", + "connection_timeout": 5, + "port": 8081, + }, + { + "address": "real2", + "port": 8080, + }, + ], + }, + ], + "vrrp": { + "snmp": "enabled", + "global_parameters": { + "startup_delay": 30, + "version": "3", + "garp": { + "interval": 30, + "master_delay": 10, + "master_refresh": 100, + "master_refresh_repeat": 200, + "master_repeat": 5, + }, + }, + "groups": [ + { + "name": "g1", + "description": "Group_1", + "disable": True, + "no_preempt": True, + "rfc3768_compatibility": True, + "interface": "eth2", + "address": ["1.1.1.1", "3.3.3.3", "5.5.5.5 interface eth2"], + "advertise_interval": 10, + "peer_address": "192.168.1.3", + "priority": 100, + "vrid": 20, + "garp": { + "interval": 20, + "master_delay": 5, + "master_refresh": 50, + "master_refresh_repeat": 100, + "master_repeat": 3, + }, + "authentication": { + "type": "plaintext-password", + "password": "testpass", + }, + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.5", + "script": "script.sh", + }, + "track": { + "exclude_vrrp_interface": True, + "interface": ["eth1"], + }, + "excluded_address": [ + "192.168.1.7 interface eth3", + "192.168.1.8", + ], + "hello_source_address": "192.168.1.2", + }, + ], + "sync_groups": [ + { + "name": "sg1", + "member": ["g1"], + "transition_script": { + "master": "/var/tmp/script.sh", + "backup": "/var/tmp/script.sh", + "fault": "/var/tmp/script.sh", + "stop": "/var/tmp/script.sh", + }, + "health_check": { + "failure_count": 3, + "interval": 10, + "ping": "192.168.1.1", + "script": "/var/tmp/script.sh", + }, + }, + ], + }, + } + self.assertEqual(gathered_list, result["gathered"]) + + def test_vrrp_groups_deleted(self): + set_module_args( + dict( + config=dict( + vrrp=dict( + groups=[ + dict(name="g1"), + ], + sync_groups=[ + dict(name="sg1"), + ], + ), + ), + state="deleted", + ), + ) + commands = [ + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group sg1", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_objs_deleted(self): + set_module_args( + dict( + config=dict( + disable=False, + vrrp=dict( + global_parameters=dict( + startup_delay=32, + garp=dict(), + ), + groups=[ + dict(name="g1"), + ], + sync_groups=[], + ), + virtual_servers=[], + ), + state="deleted", + ), + ) + commands = [ + "delete high-availability virtual-server", + "delete high-availability vrrp global-parameters garp", + "delete high-availability vrrp global-parameters startup-delay", + "delete high-availability vrrp group g1", + "delete high-availability vrrp sync-group", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_deleted_all(self): + set_module_args( + dict( + config=dict(), + state="deleted", + ), + ) + commands = [ + "delete high-availability", + ] + self.execute_module(changed=True, commands=commands) + + def test_vrrp_purged(self): + set_module_args( + dict( + config=dict(), + state="purged", + ), + ) + commands = [ + "delete high-availability", + ] + self.execute_module(changed=True, commands=commands) |
