Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add Support for LAGG Interfaces #83

Merged
merged 3 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/modules/2_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/source/modules/2_reload.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
84 changes: 84 additions & 0 deletions docs/source/modules/interface.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
**********

Expand Down Expand Up @@ -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
********

Expand Down Expand Up @@ -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'
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions plugins/module_utils/main/interface_lagg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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):
FIELD_ID = 'device'
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 = [FIELD_ID]
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!")
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)

self._base_check()

def update(self) -> None:
self.b.update(enable_switch=False)
100 changes: 100 additions & 0 deletions plugins/modules/interface_lagg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (C) 2024, AnsibleGuy <[email protected]>
# 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', 'parent'],
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, aliases=['fast_timeout'],
description='Enable lacp fast-timeout on the interface.'
),
use_flowid=dict(
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, 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=['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']),
**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()
6 changes: 5 additions & 1 deletion plugins/modules/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]


Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions plugins/modules/reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def run_module():
'interface_vlan',
'interface_vxlan',
'interface_vip',
'interface_lagg',
'frr',
'webproxy',
'bind',
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions tests/cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand Down
Loading
Loading