I like to have a LAAG configuration option therefore I start to create my own modul and i like to share with you guys.
maybe it is good enought for copy paste into your next Version or it need some adjustment.
modules/pfsense_lagg.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2023, Example
# 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
DOCUMENTATION = r"""
---
module: pfsense_lagg
version_added: "0.1.0"
author:
- "Your Name (@your_github_handle)"
short_description: Manage pfSense LAGG (Link Aggregations)
description:
- This module manages pfSense LAGG interfaces (Link Aggregation). It can create, update, or remove LAGGs.
options:
laggif:
description:
- The name of the LAGG interface (e.g. C(lagg0), C(lagg1), etc.).
- Make sure this matches what pfSense actually stores in its config (case sensitivity may matter unless you
implement case-insensitive matching in your code).
required: true
type: str
members:
description:
- A list of physical interfaces to be aggregated, e.g. C(['igb0','igb1']).
- They must exist on the pfSense device and should not be part of any other LAGG.
required: true
type: list
elements: str
proto:
description:
- The LAGG protocol to use. Available protocols:
- C(none): Disables any traffic on this LAGG without disabling the interface itself.
- C(lacp): Uses the IEEE 802.3ad Link Aggregation Control Protocol (LACP) and the Marker Protocol.
Negotiates aggregable links with the peer into one or more Link Aggregated Groups.
- C(failover): Sends and receives traffic through the master port only. If the master port
becomes unavailable, the next active port is used.
- C(loadbalance): Balances outgoing traffic across active ports based on hashed protocol header
information, and accepts incoming traffic from any active port. (Static setup, no dynamic negotiation.)
- C(roundrobin): Distributes outgoing traffic in a round-robin fashion through all active ports,
and accepts incoming traffic from any active port.
choices: ["none", "lacp", "failover", "loadbalance", "roundrobin"]
default: "lacp"
type: str
lacptimeout:
description:
- LACP timeout mode (only relevant if proto = lacp).
- C(fast) or C(slow). Typically defaults to fast on pfSense.
choices: ["fast", "slow"]
default: "fast"
type: str
lagghash:
description:
- Hash method for load distribution.
- Possible options are:
- (l2,l3,l4) layer 2/3/4 (default)
- (l2) layer 2 (MAC addresses)
- (l3) layer 3 (IP addresses)
- (l4) layer 4 (Port numbers)
- (l2,l3) layer 2/3 (MAC + IP)
- (l3,l4) layer 3/4 (IP + Port)
- (l2,l4) layer 2/4 (MAC + Port)
default: "l2,l3,l4"
type: str
descr:
description:
- Description for the LAGG interface, for reference only (not parsed except for display).
default: ""
type: str
state:
description:
- Whether the LAGG should be present (created/updated) or absent (removed).
choices: ["present", "absent"]
default: "present"
type: str
"""
EXAMPLES = r"""
- name: Create a LAGG (lacp) with igb0 and igb1
pfsense_lagg:
laggif: lagg1
members:
- igb0
- igb1
proto: lacp
lacptimeout: fast
lagghash: "l2,l3,l4"
descr: "WAN-LACP"
state: present
- name: Remove that LAGG
pfsense_lagg:
laggif: lagg1
members:
- igb0
- igb1
state: absent
"""
RETURN = r"""
commands:
description: A list of pseudo-CLI commands that the module generated (for debugging purposes).
returned: always
type: list
sample:
- "create lagg 'lagg1', proto='lacp', members='igb0,igb1'"
- "update lagg 'lagg1', set proto='failover'"
- "delete lagg 'lagg1'"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.pfsensible.core.plugins.module_utils.lagg import (
PFSenseLaggModule,
LAGG_ARGUMENT_SPEC
)
def main():
module = AnsibleModule(
argument_spec=LAGG_ARGUMENT_SPEC,
supports_check_mode=True
)
pfmodule = PFSenseLaggModule(module)
pfmodule.run(module.params)
pfmodule.commit_changes()
if __name__ == '__main__':
main()
modules_utils/lagg.py
# -*- coding: utf-8 -*-
# Copyright: (c) 2023, Example
# 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
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
LAGG_ARGUMENT_SPEC = dict(
state=dict(default='present', choices=['present', 'absent']),
laggif=dict(required=True, type='str'), # z.B. "lagg0", "lagg1" usw.
members=dict(required=True, type='list', elements='str'),
proto=dict(default='lacp', choices=['lacp', 'failover', 'loadbalance', 'roundrobin', 'none']),
lacptimeout=dict(default='fast', choices=['fast', 'slow']),
lagghash=dict(default='l2,l3,l4', type='str'),
descr=dict(default='', type='str'),
)
class PFSenseLaggModule(PFSenseModuleBase):
@staticmethod
def get_argument_spec():
return LAGG_ARGUMENT_SPEC
def __init__(self, module, pfsense=None):
super(PFSenseLaggModule, self).__init__(module, pfsense)
self.name = "pfsense_lagg"
self.root_elt = self.pfsense.get_element('laggs')
self.obj = dict()
if self.root_elt is None:
self.root_elt = self.pfsense.new_element('laggs')
self.pfsense.root.append(self.root_elt)
self.setup_lagg_cmds = ""
def _params_to_obj(self):
params = self.params
obj = dict()
obj['laggif'] = params['laggif']
obj['members'] = ",".join(params['members'])
obj['proto'] = params['proto']
obj['lacptimeout'] = params['lacptimeout']
obj['lagghash'] = params['lagghash']
obj['descr'] = params['descr']
return obj
def _validate_params(self):
if not self.params['members']:
self.module.fail_json(msg="members muss mindestens ein Interface enthalten.")
def _find_target(self):
requested = self.obj['laggif']
for lagg_node in self.root_elt.findall('lagg'):
node_laggif = lagg_node.findtext('laggif') or ''
if node_laggif.lower() == requested.lower():
self.obj['laggif'] = node_laggif
return lagg_node
return None
def _create_target(self):
return self.pfsense.new_element('lagg')
def _copy_and_add_target(self):
super(PFSenseLaggModule, self)._copy_and_add_target()
self.setup_lagg_cmds += self._cmd_create()
def _copy_and_update_target(self):
before_laggif = self.target_elt.find('laggif').text
(before, changed) = super(PFSenseLaggModule, self)._copy_and_update_target()
if changed:
self.setup_lagg_cmds += "pfSense_interface_destroy('{}');\n".format(before_laggif)
self.setup_lagg_cmds += self._cmd_create()
return (before, changed)
def _pre_remove_target_elt(self):
if self.pfsense.get_interface_by_port(self.obj['laggif']) is not None:
self.module.fail_json(
msg="LAGG {} is in use therefore you can't delete it.".format(self.obj['laggif'])
)
self.setup_lagg_cmds += "pfSense_interface_destroy('{}');\n".format(self.obj['laggif'])
def _cmd_create(self):
cmd = "$lagg = array();\n"
cmd += "$lagg['laggif'] = '{}';\n".format(self.obj['laggif'])
cmd += "$lagg['members'] = '{}';\n".format(self.obj['members'])
cmd += "$lagg['descr'] = '{}';\n".format(self.obj['descr'])
cmd += "$lagg['proto'] = '{}';\n".format(self.obj['proto'])
cmd += "$lagg['lacptimeout'] = '{}';\n".format(self.obj['lacptimeout'])
cmd += "$lagg['lagghash'] = '{}';\n".format(self.obj['lagghash'])
cmd += "$laggif = interface_lagg_configure($lagg);\n"
cmd += "if (($laggif == NULL) || ($laggif != $lagg['laggif'])) {\n"
cmd += " pfSense_interface_destroy('{}');\n".format(self.obj['laggif'])
cmd += "} else {\n"
interface = self.pfsense.get_interface_by_port(self.obj['laggif'])
if interface is not None:
cmd += " interface_configure('{}', true);\n".format(interface)
cmd += "}\n"
return cmd
def get_update_cmds(self):
cmd = 'require_once("filter.inc");\n'
if self.setup_lagg_cmds:
cmd += 'require_once("interfaces.inc");\n'
cmd += self.setup_lagg_cmds
cmd += "if (filter_configure() == 0) { clear_subsystem_dirty('filter'); }\n"
return cmd
def _update(self):
return self.pfsense.phpshell(self.get_update_cmds())
def _get_obj_name(self):
return "'{}'".format(self.obj['laggif'])
def _log_fields(self, before=None):
vals = ''
if before is None:
# Neu
vals += self.format_cli_field(self.obj, 'proto')
vals += self.format_cli_field(self.obj, 'members')
vals += self.format_cli_field(self.obj, 'descr')
else:
vals += self.format_updated_cli_field(self.obj, before, 'proto', add_comma=(vals))
vals += self.format_updated_cli_field(self.obj, before, 'members', add_comma=(vals))
vals += self.format_updated_cli_field(self.obj, before, 'descr', add_comma=(vals))
return vals
please let me know if there are some questions or unclear.
And if you can give me an feedback it will be great.
Thanks and Regards
Diogo Ferrario
I like to have a LAAG configuration option therefore I start to create my own modul and i like to share with you guys.
maybe it is good enought for copy paste into your next Version or it need some adjustment.
modules/pfsense_lagg.py
modules_utils/lagg.py
please let me know if there are some questions or unclear.
And if you can give me an feedback it will be great.
Thanks and Regards
Diogo Ferrario