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
 | 
			
		||||
from libnmstate import netapplier
 | 
			
		||||
from libnmstate import netinfo
 | 
			
		||||
from libnmstate.schema import Bond
 | 
			
		||||
from libnmstate.schema import BondMode
 | 
			
		||||
from libnmstate.schema import DNS
 | 
			
		||||
from libnmstate.schema import Ethernet
 | 
			
		||||
from libnmstate.schema import Ethtool
 | 
			
		||||
@@ -130,6 +132,37 @@ def _add_sub_tree(data, subtree):
 | 
			
		||||
    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):
 | 
			
		||||
    if address.lower() == 'any' or address.lower() == 'all':
 | 
			
		||||
        return True
 | 
			
		||||
@@ -145,6 +178,8 @@ class NmstateNetConfig(os_net_config.NetConfig):
 | 
			
		||||
        self.route_data = {}
 | 
			
		||||
        self.rules_data = []
 | 
			
		||||
        self.dns_data = {'server': [], 'domain': []}
 | 
			
		||||
        self.linuxbond_data = {}
 | 
			
		||||
        self.member_names = {}
 | 
			
		||||
        self.route_table_data = {}
 | 
			
		||||
        logger.info('nmstate net config provider created.')
 | 
			
		||||
 | 
			
		||||
@@ -556,48 +591,13 @@ class NmstateNetConfig(os_net_config.NetConfig):
 | 
			
		||||
        else:
 | 
			
		||||
            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 not base_opt.hotplug:
 | 
			
		||||
                logger.info('Using NetworkManager, hotplug is always set to'
 | 
			
		||||
                            '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:
 | 
			
		||||
            data[Interface.MTU] = base_opt.mtu
 | 
			
		||||
@@ -810,14 +810,43 @@ class NmstateNetConfig(os_net_config.NetConfig):
 | 
			
		||||
                                    interface.ethtool_opts)
 | 
			
		||||
 | 
			
		||||
        if interface.renamed:
 | 
			
		||||
            # TODO(Karthik S) Handle renamed interfaces
 | 
			
		||||
            pass
 | 
			
		||||
            logger.info(f"Interface {interface.hwname} being renamed to"
 | 
			
		||||
                        f"{interface.name}")
 | 
			
		||||
            self.renamed_interfaces[interface.hwname] = interface.name
 | 
			
		||||
        if interface.hwaddr:
 | 
			
		||||
            data[Interface.MAC] = interface.hwaddr
 | 
			
		||||
 | 
			
		||||
        logger.debug(f'interface data: {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):
 | 
			
		||||
        """Apply the network configuration.
 | 
			
		||||
 | 
			
		||||
@@ -852,6 +881,17 @@ class NmstateNetConfig(os_net_config.NetConfig):
 | 
			
		||||
            logger.info(f'Routes_data {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 not self.noop:
 | 
			
		||||
                try:
 | 
			
		||||
@@ -889,4 +929,6 @@ class NmstateNetConfig(os_net_config.NetConfig):
 | 
			
		||||
                raise os_net_config.ConfigurationError(message)
 | 
			
		||||
 | 
			
		||||
        self.interface_data = {}
 | 
			
		||||
        self.linuxbond_data = {}
 | 
			
		||||
 | 
			
		||||
        return updated_interfaces
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,9 @@ class TestNmstateNetConfig(base.TestCase):
 | 
			
		||||
    def get_interface_config(self, name='em1'):
 | 
			
		||||
        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):
 | 
			
		||||
        data = {}
 | 
			
		||||
        data[Ethernet.CONFIG_SUBTREE] = \
 | 
			
		||||
@@ -571,6 +574,105 @@ class TestNmstateNetConfig(base.TestCase):
 | 
			
		||||
        self.assertEqual(yaml.safe_load(expected_route_table),
 | 
			
		||||
                         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):
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user