Skip to content
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
57 changes: 57 additions & 0 deletions docs/module/lag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Link Aggregation Group (LAG) Configuration Module

This configuration module configures link bonding parameters, for LAGs between 2 devices (i.e. not MC-LAG)

(lag-platform)=
LAG is currently supported on these platforms:

| Operating system | lag | LACP off | LACP passive
| --------------------- | :-------: | :--------: | :----------:
| Cumulus Linux | ✅ | ✅ | ❌
| FRR | ✅ | ✅ | ❌

## Parameters

The following parameters can be set globally or per node/link:

* **mode**: lag mode, one of "802.3ad" (default), "balance-xor" or "active-backup"
* **lacp**: LACP protocol interval: "fast", "slow" or "off"

Note that 'link down' is not easily detectable in a virtual environment with veth pairs, therefore it is strongly recommended
to enable LACP whenever possible

* **lacp_mode**: "active" (default) or "passive"; note that at most 1 node can be passive

The following parameters can be set per link:
* **members**: List of links that form the LAG, mandatory and formatted like **topology.links**
* **ifindex**: Optional parameter to control naming of the bonding device

By creating a link with **lag.members** defined, a *lag* type link is created with the given list of member links.

## Example

To create a LAG consisting of 2 links between devices 'r1' and 'r2':

```
module: [ lag ]

nodes: [ r1, r2 ]

links:
- lag.members: [ r1-r2, r1-r2 ]
```
Additional parameters such as vlan trunks, OSPF cost, etc. can be applied to such *lag* type links.

In case additional attributes are required for the member links, the members can be expanded:
```
links:
- lag.members:
- r1:
ifindex: 49 # Use 100G links 1/1/49 and 1/1/50
r2:
ifindex: 49
- r1:
ifindex: 50
r2:
ifindex: 50
```
1 change: 1 addition & 0 deletions netsim/ansible/tasks/frr/initial-clab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
modprobe vrf || echo "FAILED"
become: true
delegate_to: localhost
run_once: true
tags: [ print_action, always ]
register: modprobe_result
ignore_errors: True
Expand Down
6 changes: 3 additions & 3 deletions netsim/ansible/templates/dhcp/cumulus.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Disable IPv6 RA on DHCPv6 client interfaces
#
{% for l in interfaces if l.type in ['lan','p2p','stub'] and l.dhcp.client.ipv6 is defined %}
{% for l in interfaces if l.type in ['lan','p2p','stub','lag'] and l.dhcp.client.ipv6 is defined %}
{% if loop.first %}
echo "Disable IPv6 RA"
cat >/tmp/config <<CONFIG
Expand All @@ -21,7 +21,7 @@ vtysh -f /tmp/config
#
set -e
#
{% for l in interfaces if l.type in ['lan','p2p','stub'] and l.dhcp.client is defined %}
{% for l in interfaces if l.type in ['lan','p2p','stub','lag'] and l.dhcp.client is defined %}
{% if loop.first %}
echo "DHCP: set IP addresses on interfaces"
cat >/etc/network/interfaces.d/12-dhcp.intf <<CONFIG
Expand All @@ -43,7 +43,7 @@ CONFIG
{% endif %}
{% endfor %}
#
{% for l in interfaces if l.type in ['lan','p2p','stub'] and l.dhcp.client is defined %}
{% for l in interfaces if l.type in ['lan','p2p','stub','lag'] and l.dhcp.client is defined %}
{% if loop.first %}
echo "DHCP: executing ifup"
nohup bash -c 'ifreload -a || ifreload -a' &
Expand Down
4 changes: 2 additions & 2 deletions netsim/ansible/templates/initial/cumulus.j2
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ done
#
echo "INIT: creating other interface"
cat >/etc/network/interfaces.d/11-physical.intf <<CONFIG
{% for l in interfaces if l.type in ['lan','p2p','stub'] %}
{% for l in interfaces if l.type in ['lan','p2p','stub','lag'] %}
auto {{ l.ifname }}
{% if l.ipv4 is defined %}

Expand Down Expand Up @@ -109,7 +109,7 @@ done
#
# For whatever crazy reason, I had to enable IPv6 in containers
#
{% for l in interfaces if l.type in ['lan','p2p','stub'] and (l.ipv6 is defined or 'external' in l.role|default('')) %}
{% for l in interfaces if l.type in ['lan','p2p','stub','lag'] and (l.ipv6 is defined or 'external' in l.role|default('')) %}
sysctl -qw net.ipv6.conf.{{ l.ifname }}.disable_ipv6=0
{% endfor %}
#
Expand Down
3 changes: 2 additions & 1 deletion netsim/ansible/templates/initial/frr.j2
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ ip link set {{ l.ifname }} up
echo "service integrated-vtysh-config" >/etc/frr/vtysh.conf
#
# Set Ethernet interface MTU
{% for l in interfaces if l.mtu is defined %}
{% for l in interfaces if l.mtu is defined and l.get('type',"")!='lag' %}
ip link set {{ l.ifname }} mtu {{ l.mtu }}
{% endfor %}

#
# Rest of initial configuration done through VTYSH
#
Expand Down
33 changes: 33 additions & 0 deletions netsim/ansible/templates/lag/cumulus.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
#
set -e

echo "LAG: creating bond interface(s)"
#
# Create bond interface entry
#
{%- macro bond_interface(data) %}
auto {{ data.ifname }}
iface {{ data.ifname }}
pre-up ip link add {{ data.ifname }} type bond
bond-slaves {%- for i in interfaces if 'parentindex' in i and i.parentindex==data.linkindex %} {{ i.ifname }}{%- endfor %}
{% set _lacp = data.lag.lacp|default(lag.lacp) %}
{% if _lacp=='slow' %}
bond-lacp-rate slow
{% elif _lacp=='off' or data.lag.mode|default(lag.mode)=="balance-xor" %}
bond-mode balance-xor
{% endif %}
{% endmacro %}

cat >/etc/network/interfaces.d/20-bond.intf <<CONFIG
{% for l in interfaces if l.type == 'lag' %}
{{ bond_interface(l) }}
{% endfor %}
CONFIG

#
echo "LAG: executing ifreload"
#
until ifreload -a; do
sleep 1
done
31 changes: 31 additions & 0 deletions netsim/ansible/templates/lag/frr.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
#
set -e # Exit immediately when any command fails
#

#
# Create bonds for LAGs, if any. Requires kernel bonding module loaded
#
{% for l in interfaces if 'lag' in l %}
{% if l.type=='lag' %}
{% set _m = l.lag.mode|default(lag.mode) %}
{% if _m=="802.3ad" %}
{% set _lacp = l.lag.lacp|default(lag.lacp) %}
{% set lacp_act = 'off' if _lacp=='off' else 'on' %}
{% set lacp_rate = (' lacp_rate ' + _lacp) if _lacp!='off' else '' %}
{% set _m = _m + " xmit_hash_policy encap3+4 lacp_active " + lacp_act + lacp_rate %}
{% endif %}
ip link add dev {{l.ifname}} type bond mode {{_m}}
{% endif %}
ip link set dev {{ l.ifname }} down
{% endfor %}

{% for l in interfaces if 'lag' in l %}
{% if l.type=='p2p' %}
ip link set dev {{ l.ifname }} master {% for i in interfaces if i.type=='lag' and i.linkindex==l.parentindex %}{{ i.ifname }}
{% endfor %}
{% endif %}
ip link set dev {{ l.ifname }} up
{% endfor %}

exit 0
9 changes: 4 additions & 5 deletions netsim/ansible/templates/vlan/cumulus.j2
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ auto bridge
iface bridge
bridge-vlan-aware yes
{% if vlans is defined %}
{% for vdata in vlans.values() %}
bridge-vids {{ vdata.id }}
{% endfor %}
{% set vids = vlans.values() | map(attribute='id') | sort | map('string') %}
bridge-vids {{ ",".join(vids) }}
{% endif %}
{% for ifdata in interfaces if ifdata.virtual_interface is not defined and ifdata.vlan is defined %}
{% for ifdata in interfaces if ifdata.vlan is defined and (ifdata.virtual_interface is not defined or ifdata.type=="lag") %}
bridge-ports {{ ifdata.ifname }}
{% endfor %}
CONFIG
Expand All @@ -35,7 +34,7 @@ cat >/etc/network/interfaces.d/51-bridge-interfaces.intf <<CONFIG
auto {{ ifdata.ifname }}
{% endif %}
iface {{ ifdata.ifname }}
{% if ifdata.vlan.trunk_id is defined %}
{% if ifdata.vlan.trunk_id is defined and ifdata.type != "lag" %}
bridge-vids {{ ifdata.vlan.trunk_id|sort|join(",") }}
{% if ifdata.vlan.native is defined %}
bridge-pvid {{ ifdata.vlan.access_id }}
Expand Down
7 changes: 4 additions & 3 deletions netsim/augment/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from . import devices,addressing

VIRTUAL_INTERFACE_TYPES: typing.Final[typing.List[str]] = [
'loopback', 'tunnel' ]
'loopback', 'tunnel', 'lag' ]

def adjust_interface_list(iflist: list, link: Box, nodes: Box) -> list:
link_intf = []
Expand Down Expand Up @@ -381,6 +381,7 @@ def assign_link_prefix(
addr_pools: Box,
nodes: Box,
link_path: str = 'links') -> Box:

if 'prefix' in link: # User specified a static link prefix
pfx_list = addressing.parse_prefix(link.prefix,path=link_path)
if log.debug_active('addr'): # pragma: no cover (debugging printout)
Expand Down Expand Up @@ -851,7 +852,7 @@ def check_link_type(data: Box) -> bool:

if link_type == 'loopback' and node_cnt != 1:
log.error(
f'Looopback link {data._linkname} can have a single node attached\n... {data}',
f'Loopback link {data._linkname} can have a single node attached\n... {data}',
log.IncorrectValue,
'links')
return False
Expand Down Expand Up @@ -1082,7 +1083,7 @@ def transform(link_list: typing.Optional[Box], defaults: Box, nodes: Box, pools:
continue

set_link_bridge_name(link,defaults)
link_default_pools = ['p2p','lan'] if link.type == 'p2p' else ['lan']
link_default_pools = ['p2p','lan'] if link.type in ['p2p','lag'] else ['lan']
assign_link_prefix(link,link_default_pools,pools,nodes,link._linkname)
copy_link_gateway(link,nodes)
assign_interface_addresses(link,pools,nodes,defaults)
Expand Down
2 changes: 1 addition & 1 deletion netsim/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def parser_add_debug(parser: argparse.ArgumentParser) -> None:
choices=sorted([
'all','addr','cli','links','libvirt','clab','modules','plugin','template',
'vlan','vrf','quirks','validate','addressing','groups','status',
'external','defaults']),
'external','defaults','lag']),
help=argparse.SUPPRESS)
parser.add_argument('--test', dest='test', action='store',nargs='*',
choices=['errors'],
Expand Down
2 changes: 1 addition & 1 deletion netsim/defaults/attributes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ link: # Global link attributes
_alt_types: [ bool, prefix_str, named_pfx ]
role: id
pool: id
type: { type: str, valid_values: [ lan, p2p, stub, loopback, tunnel, vlan_member ] }
type: { type: str, valid_values: [ lan, p2p, stub, loopback, tunnel, vlan_member, lag ] }
unnumbered: bool
interfaces:
mtu: { type: int, min_value: 64, max_value: 65535 }
Expand Down
3 changes: 3 additions & 0 deletions netsim/devices/cumulus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: Cumulus VX 4.x or 5.x configured without NVUE
interface_name: swp{ifindex}
loopback_interface_name: lo{ifindex if ifindex else ""}
tunnel_interface_name: "tun{ifindex}"
lag_interface_name: "bond{lag.ifindex}"
mgmt_if: eth0
libvirt:
image: CumulusCommunity/cumulus-vx:4.4.5
Expand Down Expand Up @@ -63,6 +64,8 @@ features:
asymmetrical_irb: True
gateway:
protocol: [ anycast, vrrp ]
lag:
passive: False
ospf:
unnumbered: True
import: [ bgp, ripv2, connected, vrf ]
Expand Down
3 changes: 3 additions & 0 deletions netsim/devices/frr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ interface_name: eth{ifindex}
mgmt_if: eth0
loopback_interface_name: lo{ifindex if ifindex else ""}
tunnel_interface_name: "tun{ifindex}"
lag_interface_name: "bond{lag.ifindex}"
routing:
_rm_per_af: True
group_vars:
Expand Down Expand Up @@ -71,6 +72,8 @@ features:
ipv4: true
ipv6: true
network: true
lag:
passive: False
mpls:
ldp: true
vpn:
Expand Down
Loading