diff --git a/os_net_config/impl_nmstate.py b/os_net_config/impl_nmstate.py index 3462ac8a..8ac8ed14 100644 --- a/os_net_config/impl_nmstate.py +++ b/os_net_config/impl_nmstate.py @@ -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 diff --git a/os_net_config/tests/test_impl_nmstate.py b/os_net_config/tests/test_impl_nmstate.py index c15e42be..e4887860 100644 --- a/os_net_config/tests/test_impl_nmstate.py +++ b/os_net_config/tests/test_impl_nmstate.py @@ -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):