Add linux bond support for nmstate provider

Extend the support for linux bonds in nmstate provider.

Change-Id: I7602b121f3ad0f86e6925208d7691b4faff24686
This commit is contained in:
Karthik S
2023-05-05 08:20:04 +05:30
parent 0350a82f19
commit 447d07550d
2 changed files with 184 additions and 40 deletions

View File

@@ -17,6 +17,8 @@
import copy import copy
from libnmstate import netapplier from libnmstate import netapplier
from libnmstate import netinfo from libnmstate import netinfo
from libnmstate.schema import Bond
from libnmstate.schema import BondMode
from libnmstate.schema import DNS from libnmstate.schema import DNS
from libnmstate.schema import Ethernet from libnmstate.schema import Ethernet
from libnmstate.schema import Ethtool from libnmstate.schema import Ethtool
@@ -130,6 +132,37 @@ def _add_sub_tree(data, subtree):
return config return config
def parse_bonding_options(bond_options_str):
bond_options_dict = {}
if bond_options_str:
options = re.findall(r'(.+?)=(.+?)($|\s)', bond_options_str)
for option in options:
bond_options_dict[option[0]] = _get_type_value(option[1])
return bond_options_dict
def set_linux_bonding_options(bond_options, primary_iface=None):
linux_bond_options = ["updelay", "miimon", "lacp_rate"]
bond_data = {Bond.MODE: BondMode.ACTIVE_BACKUP,
Bond.OPTIONS_SUBTREE: {},
Bond.PORT: []}
bond_options_data = {}
if 'mode' in bond_options:
bond_data[Bond.MODE] = bond_options['mode']
for options in linux_bond_options:
if options in bond_options:
bond_options_data[options] = bond_options[options]
bond_data[Bond.OPTIONS_SUBTREE] = bond_options_data
if primary_iface and bond_data[Bond.MODE] == BondMode.ACTIVE_BACKUP:
bond_options_data['primary'] = primary_iface
if len(bond_data[Bond.OPTIONS_SUBTREE]) == 0:
del bond_data[Bond.OPTIONS_SUBTREE]
return bond_data
def _is_any_ip_addr(address): def _is_any_ip_addr(address):
if address.lower() == 'any' or address.lower() == 'all': if address.lower() == 'any' or address.lower() == 'all':
return True return True
@@ -145,6 +178,8 @@ class NmstateNetConfig(os_net_config.NetConfig):
self.route_data = {} self.route_data = {}
self.rules_data = [] self.rules_data = []
self.dns_data = {'server': [], 'domain': []} self.dns_data = {'server': [], 'domain': []}
self.linuxbond_data = {}
self.member_names = {}
self.route_table_data = {} self.route_table_data = {}
logger.info('nmstate net config provider created.') logger.info('nmstate net config provider created.')
@@ -556,48 +591,13 @@ class NmstateNetConfig(os_net_config.NetConfig):
else: else:
data[Interface.STATE] = InterfaceState.DOWN data[Interface.STATE] = InterfaceState.DOWN
if not base_opt.nm_controlled:
logger.info('Using NetworkManager, nm_controlled is always true.'
'Deprecating it from next release')
if isinstance(base_opt, objects.Interface): if isinstance(base_opt, objects.Interface):
if not base_opt.hotplug: if not base_opt.hotplug:
logger.info('Using NetworkManager, hotplug is always set to' logger.info('Using NetworkManager, hotplug is always set to'
'true. Deprecating it from next release') 'true. Deprecating it from next release')
elif isinstance(base_opt, objects.Vlan) or \
re.match(r'\w+\.\d+$', base_opt.name):
msg = 'Error: VLAN interfaces not yet supported by impl_nmstate'
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.IvsInterface):
msg = 'Error: IVS interfaces not yet supported by impl_nmstate'
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.NfvswitchInternal):
msg = 'Error: NFVSwitch not yet supported by impl_nmstate'
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.IbInterface):
msg = 'Error: Infiniband not yet supported by impl_nmstate'
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.OvsBond):
msg = "Error: Ovs Bonds are not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.LinuxBridge):
msg = "Error: Linux bridges are not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.LinuxTeam):
msg = "Error: Linux Teams are not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.OvsTunnel):
msg = "Error: OVS tunnels not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.OvsPatchPort):
msg = "Error: OVS tunnels not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
elif isinstance(base_opt, objects.OvsDpdkBond):
msg = "Error: OVS DPDK Bonds not yet supported by impl_nmstate"
raise os_net_config.NotImplemented(msg)
else:
msg = "Error: Unsupported interface by impl_nmstate"
raise os_net_config.NotImplemented(msg)
if not base_opt.nm_controlled:
logger.info('Using NetworkManager, nm_controlled is always true.'
'Deprecating it from next release')
if base_opt.mtu: if base_opt.mtu:
data[Interface.MTU] = base_opt.mtu data[Interface.MTU] = base_opt.mtu
@@ -810,14 +810,43 @@ class NmstateNetConfig(os_net_config.NetConfig):
interface.ethtool_opts) interface.ethtool_opts)
if interface.renamed: if interface.renamed:
# TODO(Karthik S) Handle renamed interfaces logger.info(f"Interface {interface.hwname} being renamed to"
pass f"{interface.name}")
self.renamed_interfaces[interface.hwname] = interface.name
if interface.hwaddr: if interface.hwaddr:
data[Interface.MAC] = interface.hwaddr data[Interface.MAC] = interface.hwaddr
logger.debug(f'interface data: {data}') logger.debug(f'interface data: {data}')
self.interface_data[interface.name] = data self.interface_data[interface.name] = data
def add_linux_bond(self, bond):
"""Add a LinuxBond object to the net config object.
:param bond: The LinuxBond object to add.
"""
logger.info('adding linux bond: %s' % bond.name)
data = self._add_common(bond)
data[Interface.TYPE] = InterfaceType.BOND
data[Interface.STATE] = InterfaceState.UP
bond_options = {}
if bond.bonding_options:
bond_options = parse_bonding_options(bond.bonding_options)
bond_data = set_linux_bonding_options(
bond_options, primary_iface=bond.primary_interface_name)
if bond_data:
data[Bond.CONFIG_SUBTREE] = bond_data
if bond.members:
members = [member.name for member in bond.members]
self.member_names[bond.name] = members
data[Bond.CONFIG_SUBTREE][Bond.PORT] = members
logger.debug('bond data: %s' % data)
self.linuxbond_data[bond.name] = data
def apply(self, cleanup=False, activate=True): def apply(self, cleanup=False, activate=True):
"""Apply the network configuration. """Apply the network configuration.
@@ -852,6 +881,17 @@ class NmstateNetConfig(os_net_config.NetConfig):
logger.info(f'Routes_data {routes_data}') logger.info(f'Routes_data {routes_data}')
apply_routes.extend(routes_data) apply_routes.extend(routes_data)
for bond_name, bond_data in self.linuxbond_data.items():
bond_state = self.iface_state(bond_name)
if not is_dict_subset(bond_state, bond_data):
updated_interfaces[bond_name] = bond_data
else:
logger.info('No changes required for bond: %s' %
bond_name)
routes_data = self.generate_routes(bond_name)
logger.info('Routes_data %s' % routes_data)
apply_routes.extend(routes_data)
if activate: if activate:
if not self.noop: if not self.noop:
try: try:
@@ -889,4 +929,6 @@ class NmstateNetConfig(os_net_config.NetConfig):
raise os_net_config.ConfigurationError(message) raise os_net_config.ConfigurationError(message)
self.interface_data = {} self.interface_data = {}
self.linuxbond_data = {}
return updated_interfaces return updated_interfaces

View File

@@ -184,6 +184,9 @@ class TestNmstateNetConfig(base.TestCase):
def get_interface_config(self, name='em1'): def get_interface_config(self, name='em1'):
return self.provider.interface_data[name] return self.provider.interface_data[name]
def get_linuxbond_config(self, name='bond0'):
return self.provider.linuxbond_data[name]
def get_nmstate_ethtool_opts(self, name): def get_nmstate_ethtool_opts(self, name):
data = {} data = {}
data[Ethernet.CONFIG_SUBTREE] = \ data[Ethernet.CONFIG_SUBTREE] = \
@@ -571,6 +574,105 @@ class TestNmstateNetConfig(base.TestCase):
self.assertEqual(yaml.safe_load(expected_route_table), self.assertEqual(yaml.safe_load(expected_route_table),
self.get_route_config('em1')) self.get_route_config('em1'))
def test_linux_bond(self):
expected_config1 = """
name: bond0
type: bond
state: up
link-aggregation:
mode: active-backup
port:
- em1
- em2
options:
primary: em1
ipv4:
auto-dns: True
enabled: True
dhcp: True
auto-routes: True
auto-gateway: True
ipv6:
enabled: False
autoconf: False
dhcp: False
"""
expected_em1_cfg = """
name: em1
state: up
ethernet: {}
ipv4:
dhcp: False
enabled: False
ipv6:
autoconf: False
dhcp: False
enabled: False
type: ethernet
"""
expected_em2_cfg = """
name: em2
state: up
ethernet: {}
ipv4:
dhcp: False
enabled: False
ipv6:
autoconf: False
dhcp: False
enabled: False
type: ethernet
"""
expected_config2 = """
name: bond1
type: bond
state: up
link-aggregation:
mode: 802.3ad
options:
miimon: 100
updelay: 1000
lacp_rate: slow
port:
- em3
- em4
ipv4:
auto-dns: True
enabled: True
dhcp: True
auto-routes: True
auto-gateway: True
ipv6:
enabled: False
autoconf: False
dhcp: False
"""
interface1 = objects.Interface('em1', primary=True)
interface2 = objects.Interface('em2')
bond = objects.LinuxBond('bond0', use_dhcp=True,
members=[interface1, interface2])
self.provider.add_linux_bond(bond)
self.provider.add_interface(interface1)
self.provider.add_interface(interface2)
self.assertEqual(yaml.safe_load(expected_config1),
self.get_linuxbond_config('bond0'))
self.assertEqual(yaml.safe_load(expected_em1_cfg),
self.get_interface_config('em1'))
self.assertEqual(yaml.safe_load(expected_em2_cfg),
self.get_interface_config('em2'))
# primary interface is used only for active-slave bonds
interface1 = objects.Interface('em3')
interface2 = objects.Interface('em4', primary=True)
bond = objects.LinuxBond('bond1', use_dhcp=True,
members=[interface1, interface2],
bonding_options="mode=802.3ad "
"lacp_rate=slow updelay=1000 miimon=100")
self.provider.add_linux_bond(bond)
self.assertEqual(yaml.safe_load(expected_config2),
self.get_linuxbond_config('bond1'))
class TestNmstateNetConfigApply(base.TestCase): class TestNmstateNetConfigApply(base.TestCase):