Skip to content

Add LAGG #169

@dio-it

Description

@dio-it

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions