Add linux bond support for nmstate provider
Extend the support for linux bonds in nmstate provider. Change-Id: I7602b121f3ad0f86e6925208d7691b4faff24686
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user