Skip to content

Commit

Permalink
Add bond interfaces supporting
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor Mineev committed Mar 9, 2021
1 parent abd30a5 commit a6725df
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 4 deletions.
99 changes: 98 additions & 1 deletion debinterface/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,99 @@ def appendPostDown(self, cmd):
"""
self._ensure_list(self._ifAttributes, "post-down", cmd)

def setBondMaster(self, master_adapter_name):
"""Set bond-master reference on slave.
Args:
master_adapter_name (str): name of master iface
"""
self._ifAttributes['bond-master'] = master_adapter_name

def setBondMode(self, mode):
"""Set bond-mode.
Args:
mode (str, int): mode of bonding
"""
if isinstance(mode, str):
mode = mode.lower().replace('_', '-')
self._ifAttributes['bond-mode'] = mode

def setBondMiimon(self, miimon):
"""Set bond-miimon parameter.
Specifies the MII link monitoring frequency in milliseconds.
Args:
miimon (int): miimon
"""
self._ifAttributes['bond-miimon'] = miimon

def setBondUpDelay(self, delay):
"""Set bond-updelay parameter.
Specifies the time, in milliseconds, to wait before enabling a slave after a link recovery has been detected.
This option is only valid for the miimon link monitor. The updelay value should be a multiple of the miimon value.
Args:
delay (int): delay in milliseconds
"""
miimon = self._ifAttributes.get('bond-miimon', 0)
if miimon and delay % miimon != 0:
raise ValueError("Updelay should be multiple of miimon value")
self._ifAttributes['bond-updelay'] = delay

def setBondDownDelay(self, delay):
"""Set bond-downdelay parameter.
Specifies the time, in milliseconds, to wait before disabling a slave after a link failure has been detected.
This option is only valid for the miimon link monitor. The updelay value should be a multiple of the miimon value.
Args:
delay (int): delay in milliseconds
"""
miimon = self._ifAttributes.get('bond-miimon', 0)
if miimon and delay % miimon != 0:
raise ValueError("Downdelay should be multiple of miimon value")
self._ifAttributes['bond-downdelay'] = delay

def setBondPrimary(self, primary_name):
"""Set bond-primary parameter.
A string specifying which slave is the primary device.
The specified device will always be the active slave while it is available.
Only when the primary is off-line will alternate devices be used.
This is useful when one slave is preferred over another, e.g., when one slave has higher throughput than another.
The primary option is only valid for active-backup (1) mode.
Args:
primary_name (str): name of primary slave
"""
self._ifAttributes['bond-primary'] = primary_name

def setBondSlaves(self, slaves):
"""Set bond-primary parameter.
Slave interfaces names
Args:
slaves (list, optional): names of slaves
"""
if not isinstance(slaves, list):
slaves = [slaves]
self._ifAttributes['bond-slaves'] = slaves

def setUnknown(self, key, val):
"""Stores uncommon options as there are with no special handling
It's impossible to know about all available options
Format key with lower case. Replaces '_' with '-'
Args:
key (str): the option name
val (any): the option value
"""
key = key.lower().replace('_', '-')
self.setUnknownUnformatted(key, val)

def setUnknownUnformatted(self, key, val):
"""Stores uncommon options as there are with no special handling
It's impossible to know about all available options
Stores key as is
Args:
key (str): the option name
Expand Down Expand Up @@ -515,7 +605,14 @@ def set_options(self, options):
'hostapd': self.setHostapd,
'dns-nameservers': self.setDnsNameservers,
'dns-search': self.setDnsSearch,
'wpa-conf': self.setWpaConf
'wpa-conf': self.setWpaConf,
'bond-master': self.setBondMaster,
'bond-slaves': self.setBondSlaves,
'bond-miimon': self.setBondMiimon,
'bond-mode': self.setBondMode,
'bond-updelay': self.setBondUpDelay,
'bond-downdelay': self.setBondDownDelay,
'bond-primary': self.setBondPrimary
}
for key, value in options.items():
if key in roseta:
Expand Down
6 changes: 5 additions & 1 deletion debinterface/adapterValidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@
'post-up': {'type': list},
'down': {'type': list},
'pre-down': {'type': list},
'post-down': {'type': list}
'post-down': {'type': list},
'bond-mode': {'in': ['balance-rr', 'active-backup', 'balance-xor', 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb', 0, 1, 2, 3, 4, 5, 6]},
'bond-miimon': {'type': int},
'bond-updelay': {'type': int},
'bond-downdelay': {'type': int}
}
REQUIRED_FAMILY_OPTS = {
"inet": {
Expand Down
64 changes: 64 additions & 0 deletions debinterface/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,67 @@ def _set_paths(self, interfaces_path, backup_path):
else:
# self._interfaces_path is never None
self._backup_path = self._interfaces_path + ".bak"

def addBond(self, name="bond0", mode=0, slaves=None):
if not slaves:
slaves = []

if self.getAdapter(name) is not None:
raise ValueError("Interface {} already exists".format(name))

for slave in slaves:
adapter = self.getAdapter(slave)
if adapter is None:
raise ValueError("Interface {} does not exists".format(slave))
if 'bond-master' in adapter.attributes and adapter.attributes['bond-master'] != name:
raise ValueError("Interface {} already has master iface {}".format(slave, adapter.attributes['bond-master']))
adapter.attributes['bond-master'] = name

return self.addAdapter({"name": name, 'bond-mode': mode, 'bond-slaves': ' '.join(slaves)})

def validateBondSettings(self):
for adapter in self._adapters:
attrs = adapter.attributes

if 'bond-mode' in attrs:
mode = attrs['bond-mode']
if isinstance(mode, str):
mode = mode.lower().replace('_', '-')

if not 'bond-slaves' in attrs:
raise ValueError("Interface {} have no slaves".format(attrs['name']))

if mode == 1 or mode == 'active-backup':
if not 'bond-primary' in attrs:
raise ValueError("Interface {} have no primary".format(attrs['name']))
primary = self.getAdapter(attrs['bond-primary'])
if not primary:
raise ValueError(
"Interface {0} have {1} as primary, but {1} was not found".format(attrs['name'],
attrs['bond-primary']))
if primary.attributes['name'] not in attrs['bond-slaves']:
raise ValueError(
"Primary interface {} is not bond slave of {}".format(attrs['bond-primary'], attrs['name']))

if 'bond-slaves' in attrs:
slaves = attrs['bond-slaves']
if slaves != ['none']:
for slave in slaves:
slave_adapter = self.getAdapter(slave)
if not slave_adapter or slave_adapter.attributes.get('bond-master', None) != attrs['name']:
raise ValueError("Interface {} have no {} as master".format(slave, attrs['name']))

if 'bond-master' in attrs:
master = self.getAdapter(attrs['bond-master'])
if not master:
raise ValueError("Interface {} have no {} as master".format(attrs['name'], attrs['bond-master']))
master_mode = master.attributes['bond-mode']
if master_mode == 1 or master_mode == 'active-backup':
if not 'bond-primary' in attrs:
raise ValueError(
"Interface {} have no primary when bond master mode is active-backup".format(attrs['name']))
primary = self.getAdapter(attrs['bond-primary'])
if not primary:
raise ValueError(
"Interface {0} have {1} as primary, but {1} was not found".format(attrs['name'],
attrs['bond-primary']))
23 changes: 22 additions & 1 deletion debinterface/interfacesReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ def _parse_iface(self, line):

def _parse_details(self, line):
if line[0].isspace() is True:
sline = [x.strip() for x in line.split()]
sline = [x.strip() for x in line.split(None, 1)]

sline[0] = sline[0].lower().replace('_', '-')

if sline[0] == 'address':
self._adapters[self._context].setAddress(sline[1])
Expand All @@ -98,6 +100,25 @@ def _parse_details(self, line):
self._adapters[self._context].setHostapd(sline[1])
elif sline[0] == 'wpa-conf':
self._adapters[self._context].setWpaConf(sline[1])
elif sline[0] == 'bond-mode':
mode = sline[1]
try:
mode = int(mode)
except ValueError:
pass
self._adapters[self._context].setBondMode(mode)
elif sline[0] == 'bond-miimon':
self._adapters[self._context].setBondMiimon(int(sline[1]))
elif sline[0] == 'bond-updelay':
self._adapters[self._context].setBondUpDelay(int(sline[1]))
elif sline[0] == 'bond-downdelay':
self._adapters[self._context].setBondDownDelay(int(sline[1]))
elif sline[0] == 'bond-primary':
self._adapters[self._context].setBondPrimary(sline[1])
elif sline[0] == 'bond-master':
self._adapters[self._context].setBondMaster(sline[1])
elif sline[0] == 'bond-slaves':
self._adapters[self._context].setBondSlaves(sline[1].split())
elif sline[0] == 'dns-nameservers':
nameservers = sline
del nameservers[0]
Expand Down
18 changes: 18 additions & 0 deletions debinterface/interfacesWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class InterfacesWriter(object):
]
_prepFields = ['pre-up', 'pre-down', 'up', 'down', 'post-up', 'post-down']
_bridgeFields = ['ports', 'fd', 'hello', 'maxage', 'stp', 'maxwait']
_bondFields = ['bond-master', 'bond-slaves', 'bond-miimon', 'bond-updelay', 'bond-downdelay',
'bond-primary', 'bond-mode']
_plugins = ['hostapd', 'wpa-conf']

def __init__(self, adapters, interfaces_path, backup_path=None,
Expand Down Expand Up @@ -140,6 +142,7 @@ def _write_adapter(self, interfaces, adapter):
self._write_bridge(interfaces, adapter, ifAttributes)
self._write_plugins(interfaces, adapter, ifAttributes)
self._write_callbacks(interfaces, adapter, ifAttributes)
self._write_bond(interfaces, adapter, ifAttributes)
self._write_unknown(interfaces, adapter, ifAttributes)
interfaces.write("\n")

Expand All @@ -152,6 +155,21 @@ def _write_auto(self, interfaces, adapter, ifAttributes):
except KeyError:
pass

def _write_bond(self, interfaces, adapter, ifAttributes):
for field in self._bondFields:
try:
value = ifAttributes[field]
if value is not None:
if isinstance(value, list):
d = dict(varient=field,
value=" ".join(ifAttributes[field]))
else:
d = dict(varient=field, value=ifAttributes[field])
interfaces.write(self._cmd.substitute(d))
# Keep going if a field isn't provided.
except KeyError:
pass

def _write_hotplug(self, interfaces, adapter, ifAttributes):
""" Write if applicable """
try:
Expand Down
33 changes: 33 additions & 0 deletions test/interfaces_bond1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Example of bond interfaces

auto bond0
iface bond0 inet static
address 0.0.0.0
netmask 0.0.0.0
bond-slaves enp1s0 enp2s0
bond-mode 1
bond-primary enp1s0
up /sbin/ifenslave bond0 enp1s0 enp2s0
down /sbin/ifenslave -d bond0 enp1s0 enp2s0

auto enp1s0
iface enp1s0 inet manual
bond-master bond0
bond-primary enp1s0

auto enp2s0
iface enp2s0 inet manual
bond-master bond0
bond-primary enp1s0

auto bond0.10
iface bond0.10 inet static
address 192.168.10.1
netmask 255.255.255.0
vlan_raw_device bond0

auto bond0.20
iface bond0.20 inet static
address 192.168.20.1
netmask 255.255.255.0
vlan_raw_device bond0
37 changes: 36 additions & 1 deletion test/test_interfaces.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
import os
import unittest
from ..debinterface import Interfaces
from debinterface import Interfaces


INF_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "interfaces.txt")
INF2_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "interfaces2.txt")
INFBOND1_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "interfaces_bond1.txt")


class TestInterfaces(unittest.TestCase):
def test_interfaces_paths(self):
Expand Down Expand Up @@ -78,3 +80,36 @@ def test_remove_adapter_name(self):
self.assertEqual(len(itfs.adapters), nb_adapters - 1)
for adapter in itfs.adapters:
self.assertNotEqual("eth0", adapter.attributes["name"])

def test_bond_remove_bond(self):
itfs = Interfaces(interfaces_path=INFBOND1_PATH)
itfs.validateBondSettings()
itfs.removeAdapterByName("bond0")
with self.assertRaises(ValueError):
itfs.validateBondSettings()

def test_bond_configure_bond(self):
itfs = Interfaces(interfaces_path=INFBOND1_PATH)
# Slave has another interface as master
with self.assertRaises(ValueError):
itfs.addBond(name="bond1", slaves=["enp1s0"])
# Slave does not exist
with self.assertRaises(ValueError):
itfs.addBond(name="bond1", slaves=["enp3s0"])

itfs.addAdapter({"name": "enp3s0"})
itfs.addBond(name="bond1", slaves=["enp3s0"], mode=0)
itfs.validateBondSettings()

def test_bond_miimon(self):
itfs = Interfaces(interfaces_path=INFBOND1_PATH)
bond0 = itfs.getAdapter("bond0")
self.assertIsNotNone(bond0)
bond0.setBondMode("bAlAnCe_XOR")
itfs.validateBondSettings()
bond0.setBondMiimon(100)
bond0.setBondUpDelay(200)
# Delay should be multiple of the miimon value
with self.assertRaises(ValueError):
bond0.setBondDownDelay(205)

0 comments on commit a6725df

Please sign in to comment.