From a92c2a35b79406eff223d9e67df179182b0722a0 Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Sun, 6 Oct 2024 13:40:14 +0200 Subject: [PATCH 01/10] update pylint config --- .pylintrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pylintrc b/.pylintrc index 893d34b4..5941d27d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -301,6 +301,9 @@ ignored-parents= # Maximum number of arguments for function / method. max-args=8 +# Maximum number of positional arguments for function / method. +max-positional-arguments=8 + # Maximum number of attributes for a class (see R0902). max-attributes=18 From 29cdd074136ae57f5701272ed87b1c393e13a47d Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Wed, 14 Aug 2024 14:22:11 +0200 Subject: [PATCH 02/10] Implement interface_lagg module --- docs/source/modules/2_list.rst | 2 +- docs/source/modules/2_reload.rst | 2 +- docs/source/modules/interface.rst | 84 +++++++++++++++ meta/runtime.yml | 1 + plugins/module_utils/main/interface_lagg.py | 53 ++++++++++ plugins/modules/interface_lagg.py | 107 ++++++++++++++++++++ plugins/modules/list.py | 8 +- plugins/modules/reload.py | 5 + scripts/test.sh | 1 + tests/cleanup.yml | 6 ++ tests/interface_lagg.yml | 87 ++++++++++++++++ tests/list.yml | 2 +- tests/reload.yml | 1 + 13 files changed, 354 insertions(+), 5 deletions(-) create mode 100644 plugins/module_utils/main/interface_lagg.py create mode 100644 plugins/modules/interface_lagg.py create mode 100644 tests/interface_lagg.yml diff --git a/docs/source/modules/2_list.rst b/docs/source/modules/2_list.rst index d6df9a19..6a38d174 100644 --- a/docs/source/modules/2_list.rst +++ b/docs/source/modules/2_list.rst @@ -21,7 +21,7 @@ In most cases the returned type of this module ist a list of dictionaries. :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" :widths: 15 10 10 10 10 45 - "target","string","true","\-","tgt, t","What part of the running config should be queried/listed. One of: 'alias', 'rule', 'route', 'cron', 'syslog', 'package', 'unbound_general', 'unbound_acl', 'unbound_host', 'unbound_domain', 'unbound_dot', 'unbound_forward', 'unbound_host_alias', 'ipsec_cert', 'shaper_pipe', 'shaper_queue', 'shaper_rule', 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'wireguard_peer', 'interface_vlan', 'interface_vxlan', 'source_nat', 'frr_bfd', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_bgp_prefix_list', 'frr_bgp_community_list', 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_general', 'frr_ospf_prefix_list', 'frr_ospf_interface', 'frr_ospf_route_map', 'frr_ospf_network', 'frr_ospf3_general', 'frr_ospf3_interface', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_acl', 'bind_domain', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_forward', 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule'" + "target","string","true","\-","tgt, t","What part of the running config should be queried/listed. One of: 'alias', 'rule', 'route', 'cron', 'syslog', 'package', 'unbound_general', 'unbound_acl', 'unbound_host', 'unbound_domain', 'unbound_dot', 'unbound_forward', 'unbound_host_alias', 'ipsec_cert', 'shaper_pipe', 'shaper_queue', 'shaper_rule', 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'wireguard_peer', 'interface_lagg', 'interface_vlan', 'interface_vxlan', 'source_nat', 'frr_bfd', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_bgp_prefix_list', 'frr_bgp_community_list', 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_general', 'frr_ospf_prefix_list', 'frr_ospf_interface', 'frr_ospf_route_map', 'frr_ospf_network', 'frr_ospf3_general', 'frr_ospf3_interface', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_acl', 'bind_domain', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_forward', 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule'" .. include:: ../_include/param_basic.rst diff --git a/docs/source/modules/2_reload.rst b/docs/source/modules/2_reload.rst index 938724a7..51b0e64a 100644 --- a/docs/source/modules/2_reload.rst +++ b/docs/source/modules/2_reload.rst @@ -26,7 +26,7 @@ Definition :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" :widths: 15 10 10 10 10 45 - "target","string","true","\-","tgt, t","What part of the running config should be reloaded. One of: 'alias', 'rule', 'route', 'cron', 'unbound', 'syslog', 'ipsec', 'ipsec_legacy', 'shaper', 'monit', 'wireguard', 'interface_vlan', 'interface_vxlan', 'interface_vip', 'frr', 'webproxy', 'bind', 'ids'" + "target","string","true","\-","tgt, t","What part of the running config should be reloaded. One of: 'alias', 'rule', 'route', 'cron', 'unbound', 'syslog', 'ipsec', 'ipsec_legacy', 'shaper', 'monit', 'wireguard', 'interface_vlan', 'interface_vxlan', 'interface_vip', 'interface_lagg', 'frr', 'webproxy', 'bind', 'ids'" .. include:: ../_include/param_basic.rst diff --git a/docs/source/modules/interface.rst b/docs/source/modules/interface.rst index a7b63cc8..b6793615 100644 --- a/docs/source/modules/interface.rst +++ b/docs/source/modules/interface.rst @@ -39,6 +39,12 @@ ansibleguy.opnsense.interface_vip This module manages VIP configuration that can be found in the WEB-UI menu: 'Interfaces - Virtual IPs - Settings' +ansibleguy.opnsense.interface_lagg +=================================== + +This module manages LAGG configuration that can be found in the WEB-UI menu: 'Interfaces - Other Types - LAGG' + + Definition ********** @@ -95,6 +101,31 @@ ansibleguy.opnsense.interface_vip "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst +ansibleguy.opnsense.interface_lagg +================================= + +.. warning:: + + This feature is only available in OPNSense version >= 23.7 + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "match_fields","list","false","['members']","\-","Fields that are used to match configured LAGG with the running config - if any of those fields are changed, the module will think it's a new entry. At least one of: 'device', 'members', 'primary_member', 'proto', 'description'" + "device", "string", "false", "\-", "laggif", "Optional 'device' of the entry. Needs to start with 'lagg'" + "members", "list", "false", "\-", "port, int, if", "Existing LAGG capable interface - you must provide the network port as shown in 'Interfaces - Assignments - Network port'" + "primary_member", "string", "false", "\-", "\-", "This interface will be added first in the lagg making it the primary one - you must provide the network port as shown in 'Interfaces - Assignments - Network port'" + "proto", "string", "false", "lacp", "p", "The protocol to use. One of: 'none', 'lacp', 'failover', 'fec', 'loadbalance', 'roundrobin'" + "lacp_fast_timeout", "boolean", "false", "false", "\-", "Enable lacp fast-timeout on the interface." + "use_flowid", "string", "false", "\-", "\-", "Use the RSS hash from the network card if available, otherwise a hash is locally calculated. The default depends on the system tunable in net.link.lagg.default_use_flowid. One of: 'default', 'yes', 'no'" + "lagghash", "list", "false", "['l2']", "\-", "Set the packet layers to hash for aggregation protocols which load balance. At least one of: 'l2', 'l3', 'l4'" + "lacp_strict", "string", "false", "\-", "\-", "Enable lacp strict compliance on the interface. The default depends on the system tunable in net.link.lagg.lacp.default_strict_mode. One of: 'default', 'yes', 'no'" + "mtu", "integer", "false", "false", "\-", "If you leave this field blank, the smallest mtu of this laggs children will be used." + "description","string","true","\-","desc, name","The description used to match the configured entries to the existing ones" + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + + Examples ******** @@ -247,3 +278,56 @@ ansibleguy.opnsense.interface_vip interface: 'opt1' address: '192.168.0.100/24' state: 'absent' + +ansibleguy.opnsense.interface_lagg +================================= + +.. code-block:: yaml + + - hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: 'opnsense.template.ansibleguy.net' + api_credential_file: '/home/guy/.secret/opn.key' + + ansibleguy.opnsense.list: + target: 'interface_lagg' + + tasks: + - name: Example + ansibleguy.opnsense.interface_lagg: + # device: lagg0 + # description: LACP ax0/1 + members: + - ax0 + - ax1 + # primary_member: ax0 + # proto: lacp + # lacp_fast_timeout: 'default' + # use_flowid: 'default' + # lagghash: ['l2'] + # lacp_strict: 'default' + # mtu: 9000 + # match_fields: ['members'] + + - name: Adding LAGG + ansibleguy.opnsense.interface_lagg: + members: + - ax0 + - ax1 + + - name: Listing + ansibleguy.opnsense.list: + # target: 'interface_lagg' + register: existing_entries + + - name: Printing LAGGs + ansible.builtin.debug: + var: existing_entries.data + + - name: Removing LAGG + ansibleguy.opnsense.interface_vip: + device: lagg0 + match_fields: ['device'] + state: 'absent' diff --git a/meta/runtime.yml b/meta/runtime.yml index 2306884a..b8f72ccb 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -46,6 +46,7 @@ action_groups: - ansibleguy.opnsense.interface_vlan - ansibleguy.opnsense.interface_vxlan - ansibleguy.opnsense.interface_vip + - ansibleguy.opnsense.interface_lagg frr: - ansibleguy.opnsense.frr_diagnostic - ansibleguy.opnsense.frr_general diff --git a/plugins/module_utils/main/interface_lagg.py b/plugins/module_utils/main/interface_lagg.py new file mode 100644 index 00000000..a613a0d1 --- /dev/null +++ b/plugins/module_utils/main/interface_lagg.py @@ -0,0 +1,53 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Lagg(BaseModule): + CMDS = { + 'add': 'addItem', + 'del': 'delItem', + 'set': 'setItem', + 'search': 'get', + } + API_KEY_PATH = 'lagg.lagg' + API_MOD = 'interfaces' + API_CONT = 'lagg_settings' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['members', 'primary_member', 'proto', 'lacp_fast_timeout', 'use_flowid', 'lagghash', 'lacp_strict', 'mtu', 'description'] + FIELDS_ALL = ['device'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'device': 'laggif', + 'description': 'descr', + } + FIELDS_TYPING = { + 'bool': ['lacp_fast_timeout'], + 'list': ['members', 'lagghash'], + 'select': ['members', 'primary_member', 'proto', 'use_flowid', 'lagghash', 'lacp_strict'], + } + INT_VALIDATIONS = { + 'mtu': {'min': 576, 'max': 65535}, + } + EXIST_ATTR = 'lagg' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.lagg = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['members']): + self.m.fail_json("You need to provide a list of 'members' to create a lagg!") + + + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/plugins/modules/interface_lagg.py b/plugins/modules/interface_lagg.py new file mode 100644 index 00000000..5d159541 --- /dev/null +++ b/plugins/modules/interface_lagg.py @@ -0,0 +1,107 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/interfaces.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_lagg import Lagg + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/interface.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/interface.html' + + +def run_module(): + module_args = dict( + device=dict( + type='str', required=False, aliases=['laggif'], + description="Optional 'device' of the entry. Needs to start with 'lagg'", + ), + members=dict( + type='list', elements='str', required=False, aliases=['port', 'int', 'if'], + description='Existing LAGG capable interface - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'" + ), + primary_member=dict( + type='list', elements='str', required=False, + description='This interface will be added first in the lagg making it the primary one ' + "- you must provide the network port as shown in 'Interfaces - Assignments - Network port'" + ), + proto=dict( + type='str', required=False, aliases=['p'], default='lacp', + choices=['none', 'lacp', 'failover', 'fec', 'loadbalance', 'roundrobin'], + description="The protocol to use." + ), + lacp_fast_timeout=dict(type='bool', required=False, default=False, + description='Enable lacp fast-timeout on the interface.' + ), + use_flowid=dict( + type='str', required=False, choices=['default', 'yes', 'no'], + description='Use the RSS hash from the network card if available, otherwise a hash is locally calculated. ' + 'The default depends on the system tunable in net.link.lagg.default_use_flowid.' + ), + lagghash=dict( + type='list', elements='str', required=False, default=['l2'], + choices=['l2', 'l3', 'l4'], + description='Set the packet layers to hash for aggregation protocols which load balance.' + ), + lacp_strict=dict( + type='str', required=False, + choices=['default', 'yes', 'no'], + description='Enable lacp strict compliance on the interface. The default depends on the ' + 'system tunable in net.link.lagg.lacp.default_strict_mode.', + ), + mtu=dict( + type='int', required=False, + description='If you leave this field blank, the smallest mtu of this laggs children will be used.' + ), + description=dict(type='str', required=False, aliases=['desc', 'name']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured LAGG with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['device', 'members', 'primary_member', 'proto', 'description'], + default=['members'], + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Lagg(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/list.py b/plugins/modules/list.py index dd8d1d8a..cfdfcc8f 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -25,8 +25,8 @@ TARGETS = [ 'alias', 'rule', 'rule_interface_group', 'route', 'gateway', 'syslog', 'package', 'unbound_host', 'unbound_domain', 'frr_ospf_general', 'frr_ospf3_general', 'unbound_forward', 'shaper_pipe', 'shaper_queue', 'shaper_rule', - 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'bind_domain', 'wireguard_peer', 'interface_vlan', - 'unbound_host_alias', 'interface_vxlan', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', + 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'bind_domain', 'wireguard_peer', 'interface_lagg', + 'interface_vlan', 'unbound_host_alias', 'interface_vxlan', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_ospf3_interface', 'frr_ospf_interface', 'bind_acl', 'frr_ospf_network', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule', @@ -174,6 +174,10 @@ def run_module(): from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_vxlan import \ Vxlan as Target_Obj + elif target == 'interface_lagg': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_lagg import \ + Lagg as Target_Obj + elif target == 'source_nat': from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.source_nat import \ SNat as Target_Obj diff --git a/plugins/modules/reload.py b/plugins/modules/reload.py index 9e863a9e..e63a57cb 100644 --- a/plugins/modules/reload.py +++ b/plugins/modules/reload.py @@ -43,6 +43,7 @@ def run_module(): 'interface_vlan', 'interface_vxlan', 'interface_vip', + 'interface_lagg', 'frr', 'webproxy', 'bind', @@ -132,6 +133,10 @@ def run_module(): from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_vip import \ Vip as Target_Obj + elif target == 'interface_lagg': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_lagg import \ + Lagg as Target_Obj + elif target == 'frr': from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.frr_bgp_general import \ General as Target_Obj diff --git a/scripts/test.sh b/scripts/test.sh index 40ccc942..1bb772e1 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -86,6 +86,7 @@ run_test 'wireguard_show' 1 run_test 'interface_vlan' 1 run_test 'interface_vxlan' 1 run_test 'interface_vip' 1 +run_test 'interface_lagg' 1 run_test 'source_nat' 1 run_test 'frr_diagnostic' 1 run_test 'frr_general' 1 diff --git a/tests/cleanup.yml b/tests/cleanup.yml index 1da24d5c..7ad1f280 100644 --- a/tests/cleanup.yml +++ b/tests/cleanup.yml @@ -247,6 +247,12 @@ - 100 - 101 + - name: Cleanup lagg interfaces + ansibleguy.opnsense.interface_lagg: + members: + - 'vtnet0' + state: 'absent' + - name: Cleanup source-nat ansibleguy.opnsense.source_nat: description: "{{ item }}" diff --git a/tests/interface_lagg.yml b/tests/interface_lagg.yml new file mode 100644 index 00000000..46178a5f --- /dev/null +++ b/tests/interface_lagg.yml @@ -0,0 +1,87 @@ +--- + +- name: Testing LAGG interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'interface_lagg' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.opnsense.interface_lagg: + members: + - ansibletest11 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + ansibleguy.opnsense.interface_lagg: + members: + - lan + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 + ansibleguy.opnsense.interface_lagg: + description: 'ANSIBLE_TEST_1_1' + members: + - 'vtnet0' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + ansibleguy.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + members: + - 'vtnet0' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Removing 2 + ansibleguy.opnsense.interface_lagg: + members: + - 'vtnet0' + state: 'absent' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/tests/list.yml b/tests/list.yml index 54af1619..00d3d2d1 100644 --- a/tests/list.yml +++ b/tests/list.yml @@ -44,7 +44,7 @@ ansibleguy.opnsense.list: target: "{{ item }}" when: not ansible_check_mode - loop: ['interface_vxlan', 'interface_vlan', 'interface_vip'] + loop: ['interface_vxlan', 'interface_vlan', 'interface_vip', 'interface_lagg'] - name: Querying config - WireGuard ansibleguy.opnsense.list: diff --git a/tests/reload.yml b/tests/reload.yml index 727d23ee..e4ad5e9c 100644 --- a/tests/reload.yml +++ b/tests/reload.yml @@ -30,6 +30,7 @@ - 'interface_vlan' - 'interface_vxlan' - 'interface_vip' + - 'interface_lagg' - 'frr' - 'webproxy' - 'bind' From 7e5343c16b5922fb128b1f4fb760bac337396da4 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 26 Sep 2024 10:26:35 +0000 Subject: [PATCH 03/10] Implement PR feedback --- plugins/module_utils/main/interface_lagg.py | 6 ++++-- plugins/modules/interface_lagg.py | 17 +++++---------- tests/interface_lagg.yml | 24 +++++++++++++-------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/plugins/module_utils/main/interface_lagg.py b/plugins/module_utils/main/interface_lagg.py index a613a0d1..c74e5007 100644 --- a/plugins/module_utils/main/interface_lagg.py +++ b/plugins/module_utils/main/interface_lagg.py @@ -8,6 +8,7 @@ class Lagg(BaseModule): + FIELD_ID = 'device' CMDS = { 'add': 'addItem', 'del': 'delItem', @@ -19,7 +20,7 @@ class Lagg(BaseModule): API_CONT = 'lagg_settings' API_CMD_REL = 'reconfigure' FIELDS_CHANGE = ['members', 'primary_member', 'proto', 'lacp_fast_timeout', 'use_flowid', 'lagghash', 'lacp_strict', 'mtu', 'description'] - FIELDS_ALL = ['device'] + FIELDS_ALL = [FIELD_ID] FIELDS_ALL.extend(FIELDS_CHANGE) FIELDS_TRANSLATE = { 'device': 'laggif', @@ -43,7 +44,8 @@ def check(self) -> None: if self.p['state'] == 'present': if is_unset(self.p['members']): self.m.fail_json("You need to provide a list of 'members' to create a lagg!") - + if is_unset(self.p['lagghash']): + self.m.fail_json("You need to provide a list of 'lagghash' to create a lagg!") validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) diff --git a/plugins/modules/interface_lagg.py b/plugins/modules/interface_lagg.py index 5d159541..b8b18110 100644 --- a/plugins/modules/interface_lagg.py +++ b/plugins/modules/interface_lagg.py @@ -32,7 +32,7 @@ def run_module(): description="Optional 'device' of the entry. Needs to start with 'lagg'", ), members=dict( - type='list', elements='str', required=False, aliases=['port', 'int', 'if'], + type='list', elements='str', required=False, aliases=['port', 'int', 'if', 'parent'], description='Existing LAGG capable interface - you must provide the network ' "port as shown in 'Interfaces - Assignments - Network port'" ), @@ -46,22 +46,22 @@ def run_module(): choices=['none', 'lacp', 'failover', 'fec', 'loadbalance', 'roundrobin'], description="The protocol to use." ), - lacp_fast_timeout=dict(type='bool', required=False, default=False, + lacp_fast_timeout=dict(type='bool', required=False, default=False, aliases=['fast_timeout'], description='Enable lacp fast-timeout on the interface.' ), use_flowid=dict( - type='str', required=False, choices=['default', 'yes', 'no'], + type='str', required=False, choices=['yes', 'no'], aliases=['flowid'], description='Use the RSS hash from the network card if available, otherwise a hash is locally calculated. ' 'The default depends on the system tunable in net.link.lagg.default_use_flowid.' ), lagghash=dict( - type='list', elements='str', required=False, default=['l2'], + type='list', elements='str', required=False, aliases=['hash', 'hash_layers'], choices=['l2', 'l3', 'l4'], description='Set the packet layers to hash for aggregation protocols which load balance.' ), lacp_strict=dict( type='str', required=False, - choices=['default', 'yes', 'no'], + choices=['yes', 'no'], description='Enable lacp strict compliance on the interface. The default depends on the ' 'system tunable in net.link.lagg.lacp.default_strict_mode.', ), @@ -70,13 +70,6 @@ def run_module(): description='If you leave this field blank, the smallest mtu of this laggs children will be used.' ), description=dict(type='str', required=False, aliases=['desc', 'name']), - match_fields=dict( - type='list', required=False, elements='str', - description='Fields that are used to match configured LAGG with the running config - ' - "if any of those fields are changed, the module will think it's a new entry", - choices=['device', 'members', 'primary_member', 'proto', 'description'], - default=['members'], - ), **RELOAD_MOD_ARG, **STATE_ONLY_MOD_ARG, **OPN_MOD_ARGS, diff --git a/tests/interface_lagg.yml b/tests/interface_lagg.yml index 46178a5f..124891c7 100644 --- a/tests/interface_lagg.yml +++ b/tests/interface_lagg.yml @@ -12,6 +12,9 @@ ansibleguy.opnsense.list: target: 'interface_lagg' + vars: + if_lag: "{{ lookup('ansible.builtin.env', 'TEST_FINTERFACE_LAGG_IF') | default('lan', true) }}" + tasks: - name: Listing ansibleguy.opnsense.list: @@ -22,8 +25,7 @@ - name: Removing - does not exist ansibleguy.opnsense.interface_lagg: - members: - - ansibletest11 + device: 'lagg99' state: 'absent' register: opn_pre2 failed_when: > @@ -32,27 +34,32 @@ - name: Adding 1 - failing because of invalid interface (server-side) ansibleguy.opnsense.interface_lagg: + device: 'lagg0' members: - - lan + - 'DOES-NOT-EXIST' register: opn_fail1 failed_when: not opn_fail1.failed when: not ansible_check_mode - name: Adding 1 ansibleguy.opnsense.interface_lagg: + device: 'lagg0' description: 'ANSIBLE_TEST_1_1' members: - - 'vtnet0' + - '{{ if_lag }}' + lagghash: l2 register: opn1 failed_when: > opn1.failed or not opn1.changed - name: Adding 1 - nothing changed - ansibleguy.opnsense.interface_vlan: + ansibleguy.opnsense.interface_lagg: + device: 'lagg0' description: 'ANSIBLE_TEST_1_1' members: - - 'vtnet0' + - '{{ if_lag }}' + lagghash: l2 register: opn2 failed_when: > opn2.failed or @@ -64,13 +71,12 @@ register: opn3 failed_when: > 'data' not in opn3 or - opn3.data | length != 1 + opn3.data | length != 4 when: not ansible_check_mode - name: Removing 2 ansibleguy.opnsense.interface_lagg: - members: - - 'vtnet0' + device: 'lagg0' state: 'absent' register: opn4 failed_when: > From 84ca0adf4fe16fc5f05def6d9ce7e8a137d17415 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 26 Sep 2024 10:33:34 +0000 Subject: [PATCH 04/10] Fix linter warnings --- plugins/module_utils/main/interface_lagg.py | 5 ++++- plugins/modules/list.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/main/interface_lagg.py b/plugins/module_utils/main/interface_lagg.py index c74e5007..51b78098 100644 --- a/plugins/module_utils/main/interface_lagg.py +++ b/plugins/module_utils/main/interface_lagg.py @@ -19,7 +19,10 @@ class Lagg(BaseModule): API_MOD = 'interfaces' API_CONT = 'lagg_settings' API_CMD_REL = 'reconfigure' - FIELDS_CHANGE = ['members', 'primary_member', 'proto', 'lacp_fast_timeout', 'use_flowid', 'lagghash', 'lacp_strict', 'mtu', 'description'] + FIELDS_CHANGE = [ + 'members', 'primary_member', 'proto', 'lacp_fast_timeout', 'use_flowid', + 'lagghash', 'lacp_strict', 'mtu', 'description' + ] FIELDS_ALL = [FIELD_ID] FIELDS_ALL.extend(FIELDS_CHANGE) FIELDS_TRANSLATE = { diff --git a/plugins/modules/list.py b/plugins/modules/list.py index cfdfcc8f..4cfaca88 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -25,8 +25,8 @@ TARGETS = [ 'alias', 'rule', 'rule_interface_group', 'route', 'gateway', 'syslog', 'package', 'unbound_host', 'unbound_domain', 'frr_ospf_general', 'frr_ospf3_general', 'unbound_forward', 'shaper_pipe', 'shaper_queue', 'shaper_rule', - 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'bind_domain', 'wireguard_peer', 'interface_lagg', - 'interface_vlan', 'unbound_host_alias', 'interface_vxlan', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', + 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'bind_domain', 'wireguard_peer', 'interface_vlan', + 'unbound_host_alias', 'interface_vxlan', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_ospf3_interface', 'frr_ospf_interface', 'bind_acl', 'frr_ospf_network', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule', @@ -35,7 +35,7 @@ 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'nginx_upstream_server', 'ipsec_connection', 'ipsec_pool', 'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general', 'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule', - 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', + 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'interface_lagg', ] From c383a995793b2a1eac5eb72fb4e895efb2a12f9b Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Sun, 6 Oct 2024 14:42:45 +0200 Subject: [PATCH 05/10] update interface-lagg test --- tests/README.md | 10 ++++++++++ tests/interface_lagg.yml | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index ec1786ca..ade7b698 100644 --- a/tests/README.md +++ b/tests/README.md @@ -28,6 +28,8 @@ Some tests benefit from having a second network-interface available. You need to add a `opt1` dummy-interface named `TEST`. The assigned IPs do not matter. +Add another interface and leave it unassigned (`vtnet2`). + ### Internet access To perform some tests (system, ids) the test firewall needs to reach some public service: @@ -55,6 +57,14 @@ The gateway tests will not work correctly if the LAN interface mismatches. You can provide your GW lan-if via env-vars: `TEST_FIREWALL_RULE_GRP_IF` +### LAGG Interfaces + +The LAGG tests will not work correctly if the unassigned interface mismatches. + +You can provide your if via env-vars: `TEST_FIREWALL_LAGG_IF` + +And the count of existing LAGGs via `TEST_FIREWALL_LAGG_CNT` + ---- ## Run diff --git a/tests/interface_lagg.yml b/tests/interface_lagg.yml index 124891c7..900f9a9f 100644 --- a/tests/interface_lagg.yml +++ b/tests/interface_lagg.yml @@ -13,7 +13,8 @@ target: 'interface_lagg' vars: - if_lag: "{{ lookup('ansible.builtin.env', 'TEST_FINTERFACE_LAGG_IF') | default('lan', true) }}" + if_lag: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_LAGG_IF') | default('vtnet2', true) }}" + if_lag_ctn: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_LAGG_CNT') | default('1', true) }}" tasks: - name: Listing From b2c2864b36e068a47d4933e1fff22c9100b7cbb2 Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Sun, 6 Oct 2024 15:19:17 +0200 Subject: [PATCH 06/10] add interface-lagg to overview --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9d08572b..f6118907 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ not implemented => development => [testing](https://github.com/ansibleguy/collec | **Interfaces** | ansibleguy.opnsense.interface_vlan | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/interface.html) | stable | | **Interfaces** | ansibleguy.opnsense.interface_vxlan | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/interface.html) | stable | | **Interfaces** | ansibleguy.opnsense.interface_vip | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/interface.html) | stable | +| **Interfaces** | ansibleguy.opnsense.interface_lagg | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/interface.html) | unstable | | **NAT** | ansibleguy.opnsense.source_nat, ansibleguy.opnsense.snat | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/source_nat.html) | stable | | **Dynamic Routing** | ansibleguy.opnsense.frr_diagnostic | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/frr_diagnostic.html) | stable | | **Dynamic Routing** | ansibleguy.opnsense.frr_general | [Docs](https://opnsense.ansibleguy.net/en/latest/modules/frr_general.html) | stable | From e156a841a339c14b44dbc22ed0f019dab97300fa Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Wed, 2 Oct 2024 19:37:58 +0200 Subject: [PATCH 07/10] Add testcases for urltable and dynipv6host alias --- tests/alias.yml | 29 +++++++++++++++++++++++++++++ tests/alias_multi.yml | 27 ++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/alias.yml b/tests/alias.yml index 6cdf2777..bedf2918 100644 --- a/tests/alias.yml +++ b/tests/alias.yml @@ -245,12 +245,29 @@ updatefreq_days: 2 content: 'https://www.spamhaus.org/drop/drop.txt' + - name: Adding urltable - nothing changed + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE1' + type: 'urltable' + updatefreq_days: 2 + content: 'https://www.spamhaus.org/drop/drop.txt' + register: opn14 + failed_when: > + opn14.failed or + opn14.changed + when: not ansible_check_mode + - name: Updating urltable update-frequency ansibleguy.opnsense.alias: name: 'ANSIBLE_TEST_1_2_URLTABLE1' type: 'urltable' updatefreq_days: 6.5 content: 'https://www.spamhaus.org/drop/drop.txt' + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode - name: Adding multiple urltables ansibleguy.opnsense.alias: @@ -294,6 +311,17 @@ loop: - ['XXX'] + # dynipv6host + + - name: Adding dynipv6host + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_DYNIPV6HOST1' + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' + # cleanup - name: Cleanup @@ -322,6 +350,7 @@ - 'ANSIBLE_TEST_1_2_GEOIP1' - 'ANSIBLE_TEST_1_2_GEOIP2' - 'ANSIBLE_TEST_1_2_GEOIP3' + - 'ANSIBLE_TEST_1_2_DYNIPV6HOST1' - name: Testing Alias - listing hosts: localhost diff --git a/tests/alias_multi.yml b/tests/alias_multi.yml index 43d0cb68..55de5b62 100644 --- a/tests/alias_multi.yml +++ b/tests/alias_multi.yml @@ -66,6 +66,7 @@ ANSIBLE_TEST_2_8: type: 'urltable' content: ['https://www.spamhaus.org/drop/drop.txt', 'https://www.spamhaus.org/drop/edrop.txt'] + updatefreq_days: 6.5 # geoip ANSIBLE_TEST_2_9: @@ -74,6 +75,14 @@ ANSIBLE_TEST_2_10: type: 'geoip' content: ['AT', 'DE', 'CH'] + + # dynipv6host + ANSIBLE_TEST_2_11: + type: 'dynipv6host' + content: + - '::f00d' + interface: 'lan' + reload: false # geoip and urltable take LONG time register: opn2 failed_when: > @@ -97,9 +106,16 @@ ANSIBLE_TEST_2_8: type: 'urltable' content: 'https://www.spamhaus.org/drop/dropv6.txt' + updatefreq_days: 2 ANSIBLE_TEST_2_9: type: 'geoip' content: 'DE' + ANSIBLE_TEST_2_11: + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' reload: false # geoip and urltable take LONG time register: opn3 failed_when: > @@ -123,11 +139,19 @@ content: 'http://test.template.ansibleguy.net' ANSIBLE_TEST_2_8: type: 'urltable' + updatefreq_days: 2 content: 'https://www.spamhaus.org/drop/dropv6.txt' ANSIBLE_TEST_2_9: type: 'geoip' content: 'DE' + ANSIBLE_TEST_2_11: + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' reload: false # geoip and urltable take LONG time + debug: true register: opn6 failed_when: > opn6.failed or @@ -139,7 +163,7 @@ register: opn4 failed_when: > 'data' not in opn4 or - opn4.data | length != 10 + opn4.data | length != 11 when: not ansible_check_mode - name: Removing @@ -155,6 +179,7 @@ ANSIBLE_TEST_2_8: ANSIBLE_TEST_2_9: ANSIBLE_TEST_2_10: + ANSIBLE_TEST_2_11: state: 'absent' - name: Checking cleanup From a930754781570cf27f772eb6295dd173c60fec8a Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Wed, 2 Oct 2024 19:39:54 +0200 Subject: [PATCH 08/10] Fix updatefreq_days and interface parameters. Dynamicaly update the FIELDS_CHANGE to only update updatefreq_days and interface in the type they are used. --- plugins/module_utils/defaults/alias.py | 7 +++++++ plugins/module_utils/main/alias.py | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/defaults/alias.py b/plugins/module_utils/defaults/alias.py index 2417d511..8d8f9d4f 100644 --- a/plugins/module_utils/defaults/alias.py +++ b/plugins/module_utils/defaults/alias.py @@ -9,6 +9,7 @@ 'content': [], 'debug': False, 'updatefreq_days': 7.0, + 'interface': None } ALIAS_MOD_ARG_ALIASES = { @@ -18,6 +19,7 @@ 'description': ['desc'], 'state': ['st'], 'enabled': ['en'], + 'interface': ['int'] } ALIAS_MOD_ARGS = dict( @@ -39,6 +41,11 @@ description="Update frequency used by type 'urltable' in days - " "per example '0.5' for 12 hours" ), + interface=dict( + type='str', default=ALIAS_DEFAULTS['interface'], + aliases=ALIAS_MOD_ARG_ALIASES['interface'], required=False, + description=' Select the interface for the V6 dynamic IP.', + ), **STATE_MOD_ARG, **OPN_MOD_ARGS, ) diff --git a/plugins/module_utils/main/alias.py b/plugins/module_utils/main/alias.py index 866b6b2c..fd3bb448 100644 --- a/plugins/module_utils/main/alias.py +++ b/plugins/module_utils/main/alias.py @@ -7,7 +7,7 @@ from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.alias import \ validate_values, filter_builtin_alias from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ - get_simple_existing, simplify_translate + get_simple_existing, simplify_translate, is_unset from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule @@ -27,12 +27,13 @@ class Alias(BaseModule): FIELDS_CHANGE = ['content', 'description'] FIELDS_ALL = ['name', 'type', 'enabled'] FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_ALL.extend(['updatefreq_days', 'interface']) FIELDS_TRANSLATE = { 'updatefreq_days': 'updatefreq', } FIELDS_TYPING = { 'bool': ['enabled'], - 'select': ['type'], + 'select': ['type', 'interface'], } EXIST_ATTR = 'alias' JOIN_CHAR = '\n' @@ -50,6 +51,16 @@ def __init__( self.p = self.m.params if cnf is None else cnf # to allow override by alias_multi def check(self) -> None: + if self.p['type'] == 'urltable': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['updatefreq_days'] + if not is_unset(self.p['updatefreq_days']): + self.p['updatefreq_days'] = float(self.p['updatefreq_days']) + + if self.p['type'] == 'dynipv6host': + if is_unset(self.p['interface']): + self.m.fail_json('You need to provide an interface to create a dynipv6host alias!') + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['interface'] + if len(self.p['name']) > self.MAX_ALIAS_LEN: self._error( f"Alias name '{self.p['name']}' is invalid - " From 2079bec1b52285d6c93f896a0f9c811a8634ca9f Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Wed, 2 Oct 2024 19:45:12 +0200 Subject: [PATCH 09/10] Update documentation for alias --- docs/source/modules/alias.rst | 2 ++ plugins/module_utils/defaults/alias.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/modules/alias.rst b/docs/source/modules/alias.rst index 51faf55f..743b2e10 100644 --- a/docs/source/modules/alias.rst +++ b/docs/source/modules/alias.rst @@ -42,6 +42,7 @@ Definition "content","list","false for state changes, else true","\-","cont, c","Values the alias should hold" "type","string","false","'host'","t","Type of value the alias should hold. One of: 'host', 'network', 'port', 'url', 'urltable', 'geoip', 'networkgroup', 'mac', 'dynipv6host', 'internal', 'external'" "updatefreq_days","float","false","7.0","\-","Needed only for the alias-type 'urltable'. Interval to update its content. Per example: 0.5 for every 12 hours" + "interface","string","false","\-","int, if","Needed only for the alias-type 'dynipv6host'. Select the interface for the V6 dynamic IP" "reload","boolean","false","false","\-", .. include:: ../_include/param_reload.rst .. include:: ../_include/param_basic.rst @@ -73,6 +74,7 @@ Examples state: 'present' # type: 'host' # default # updatefreq_days: 3 # used only for type 'urltable' + # interface: lan # used only for the type 'dynipv6host' # ssl_ca_file: '/etc/ssl/certs/custom/ca.crt' # ssl_verify: False # api_key: !vault ... # alternative to 'api_credential_file' diff --git a/plugins/module_utils/defaults/alias.py b/plugins/module_utils/defaults/alias.py index 8d8f9d4f..adc35e51 100644 --- a/plugins/module_utils/defaults/alias.py +++ b/plugins/module_utils/defaults/alias.py @@ -19,7 +19,7 @@ 'description': ['desc'], 'state': ['st'], 'enabled': ['en'], - 'interface': ['int'] + 'interface': ['int', 'if'] } ALIAS_MOD_ARGS = dict( From 2bcb812c09cac3d489fd8076d928fbe539461863 Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Sun, 6 Oct 2024 15:21:30 +0200 Subject: [PATCH 10/10] remove debug from test --- tests/alias_multi.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/alias_multi.yml b/tests/alias_multi.yml index 55de5b62..c3871655 100644 --- a/tests/alias_multi.yml +++ b/tests/alias_multi.yml @@ -151,7 +151,6 @@ - '::f00d' interface: 'lan' reload: false # geoip and urltable take LONG time - debug: true register: opn6 failed_when: > opn6.failed or