From 04e2d5284138021b11622fa318bb491e648ba971 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 25 Feb 2021 09:56:24 +0100 Subject: [PATCH 1/2] add possibility to configure lacp bonding at install --- backend.py | 4 ++ netinterface.py | 105 ++++++++++++++++++++++++++++++-------- netutil.py | 2 + tui/installer/screens.py | 14 ++++- tui/network.py | 107 +++++++++++++++++++++++++++------------ 5 files changed, 177 insertions(+), 55 deletions(-) diff --git a/backend.py b/backend.py index d1fa2af..afbfb5c 100644 --- a/backend.py +++ b/backend.py @@ -1636,6 +1636,10 @@ def configureNetworking(mounts, admin_iface, admin_bridge, admin_config, hn_conf print >>mc, "IPv6_GATEWAY='%s'" % admin_config.ipv6_gateway if admin_config.vlan: print >>mc, "VLAN='%d'" % admin_config.vlan + if admin_config.bond_mode is not None: + print >>mc, "BOND_MODE='%d'" % admin_config.bond_mode + print >>mc, "BOND_MEMBERS='%d'" % str(admin_config.bond_members) + mc.close() if preserve_settings: diff --git a/netinterface.py b/netinterface.py index 47a9716..48ffdec 100644 --- a/netinterface.py +++ b/netinterface.py @@ -32,7 +32,7 @@ class NetInterface: Autoconf = 3 def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, - dns=None, domain=None, vlan=None): + dns=None, domain=None, vlan=None, bond_mode=None, bond_members=None): assert mode is None or mode == self.Static or mode == self.DHCP if ipaddr == '': ipaddr = None @@ -69,6 +69,11 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, self.ipv6addr = None self.ipv6_gateway = None + if bond_mode is not None: + assert bond_members is not None + self.bond_mode = bond_mode + self.bond_members = bond_members + def __repr__(self): hw = "hwaddr = '%s' " % self.hwaddr @@ -172,30 +177,88 @@ def writeRHStyleInterface(self, iface): """ Write a RedHat-style configuration entry for this interface to file object f using interface name iface. """ + def writeBondMember(index, member): + """ Write a RedHat-style configuration entry for a bond member. """ + + f = open('/etc/sysconfig/network-scripts/ifcfg-%s' % member, 'w') + f.write("NAME=%s-slave%d\n" % (iface, index)) + f.write("DEVICE=%s\n" % member) + f.write("ONBOOT=yes\n") + f.write("MASTER=%s\n" % iface) + f.write("SLAVE=yes\n") + f.write("BOOTPROTO=none\n") + f.write("Type=Ethernet\n") + f.close() + + def writeBondMaster(): + """ Write a RedHat-style configuration entry for a bond master. """ + + f = open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface, 'w') + f.write("NAME=%s\n" % iface) + f.write("DEVICE=%s\n" % iface) + f.write("ONBOOT=yes\n") + f.write("Type=Bond\n") + f.write("NOZEROCONF=yes\n") + f.write("BONDING_MASTER=yes\n") + # TODO: more mode + if self.bond_mode == "lacp": + f.write("BONDING_OPTS=\"mode=4 miimon=100\"\n") + + if self.vlan: + f.write("BOOTPROTO=none\n") + else: + # Writes ip config + if self.mode == self.DHCP: + f.write("BOOTPROTO=dhcp\n") + f.write("PERSISTENT_DHCLIENT=1\n") + else: + # CA-11825: broadcast needs to be determined for non-standard networks + bcast = self.getBroadcast() + f.write("BOOTPROTO=none\n") + f.write("IPADDR=%s\n" % self.ipaddr) + if bcast is not None: + f.write("BROADCAST=%s\n" % bcast) + f.write("NETMASK=%s\n" % self.netmask) + if self.gateway: + f.write("GATEWAY=%s\n" % self.gateway) + f.close() + + def writeIface(iface_name): + f = open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface_name, 'w') + f.write("NAME=%s\n" % iface_name) + f.write("DEVICE=%s\n" % iface_name) + f.write("ONBOOT=yes\n") + if self.mode == self.DHCP: + f.write("BOOTPROTO=dhcp\n") + f.write("PERSISTENT_DHCLIENT=1\n") + else: + # CA-11825: broadcast needs to be determined for non-standard networks + bcast = self.getBroadcast() + f.write("BOOTPROTO=none\n") + f.write("IPADDR=%s\n" % self.ipaddr) + if bcast is not None: + f.write("BROADCAST=%s\n" % bcast) + f.write("NETMASK=%s\n" % self.netmask) + if self.gateway: + f.write("GATEWAY=%s\n" % self.gateway) + if self.vlan: + f.write("VLAN=yes\n") + f.close() + assert self.modev6 is None assert self.mode - iface_vlan = self.getInterfaceName(iface) - f = open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface_vlan, 'w') - f.write("DEVICE=%s\n" % iface_vlan) - f.write("ONBOOT=yes\n") - if self.mode == self.DHCP: - f.write("BOOTPROTO=dhcp\n") - f.write("PERSISTENT_DHCLIENT=1\n") - else: - # CA-11825: broadcast needs to be determined for non-standard networks - bcast = self.getBroadcast() - f.write("BOOTPROTO=none\n") - f.write("IPADDR=%s\n" % self.ipaddr) - if bcast is not None: - f.write("BROADCAST=%s\n" % bcast) - f.write("NETMASK=%s\n" % self.netmask) - if self.gateway: - f.write("GATEWAY=%s\n" % self.gateway) - if self.vlan: - f.write("VLAN=yes\n") - f.close() + if self.bond_mode is not None: + for idx, member in enumerate(self.bond_members): + writeBondMember(idx, member) + + writeBondMaster() + if not self.vlan: + return + # No bound or bond + vlan + iface_vlan = self.getInterfaceName(iface) + writeIface(iface_vlan) def waitUntilUp(self, iface): if not self.isStatic(): diff --git a/netutil.py b/netutil.py index 00ec707..35eec55 100644 --- a/netutil.py +++ b/netutil.py @@ -30,6 +30,8 @@ def __init__(self, nic_dict): self.driver = "%s (%s)" % (nic_dict.get("Driver", ""), nic_dict.get("Driver version", "")) self.smbioslabel = nic_dict.get("SMBIOS Label", "") + self.bond_mode = nic_dict.get("Bond mode", None) + self.bond_members = nic_dict.get("Bond mode", None) def __repr__(self): return "" % (self.name, self.hwaddr) diff --git a/tui/installer/screens.py b/tui/installer/screens.py index 2b1df6c..925fce8 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -227,7 +227,17 @@ def get_admin_interface(answers): def get_admin_interface_configuration(answers): if 'net-admin-interface' not in answers: answers['net-admin-interface'] = answers['network-hardware'].keys()[0] - nic = answers['network-hardware'][answers['net-admin-interface']] + + net_admin_interface = answers['net-admin-interface'] + if type(net_admin_interface) == tuple: + # Bond case + members = net_admin_interface[1] + nic = answers['network-hardware'][members[0]] + nic.name = net_admin_interface[0] + nic.bond_members = members + nic.bond_mode = 'lacp' + else: + nic = answers['network-hardware'][net_admin_interface] defaults = None try: @@ -236,7 +246,7 @@ def get_admin_interface_configuration(answers): elif 'runtime-iface-configuration' in answers: all_dhcp, manual_config = answers['runtime-iface-configuration'] if not all_dhcp: - defaults = manual_config[answers['net-admin-interface']] + defaults = manual_config[net_admin_interface] except: pass diff --git a/tui/network.py b/tui/network.py index 4ad1c23..65c444f 100644 --- a/tui/network.py +++ b/tui/network.py @@ -136,13 +136,77 @@ def dhcp_change(): vlan_value = int(vlan_field.value()) if vlan_cb.selected() else None if bool(dhcp_rb.selected()): - answers = NetInterface(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) + answers = NetInterface(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value, bond_mode=nic.bond_mode, bond_members=nic.bond_members) else: answers = NetInterface(NetInterface.Static, nic.hwaddr, ip_field.value(), subnet_field.value(), gateway_field.value(), - dns_field.value(), vlan=vlan_value) + dns_field.value(), vlan=vlan_value, bond_mode=nic.bond_mode, bond_members=nic.bond_members) return RIGHT_FORWARDS, answers +def lentry(iface, conf): + key = iface + tag = netutil.linkUp(iface) and ' ' or ' [no link]' + text = "%s (%s)%s" % (iface, conf[iface].hwaddr, tag) + return (text, key) + +def iface_details(context, conf): + tui.update_help_line([' ', ' ']) + if context: + nic = conf[context] + + table = [ ("Name:", nic.name), + ("Driver:", nic.driver), + ("MAC Address:", nic.hwaddr), + ("PCI Details:", nic.pci_string) ] + if nic.smbioslabel != "": + table.append(("BIOS Label:", nic.smbioslabel)) + + snackutil.TableDialog(tui.screen, "Interface Details", *table) + else: + netifs_all = netutil.getNetifList(include_vlan=True) + details = map(lambda x: (x, netutil.ipaddr(x)), filter(netutil.interfaceUp, netifs_all)) + snackutil.TableDialog(tui.screen, "Networking Details", *details) + tui.screen.popHelpLine() + return True + +def lacp_bond_ui(conf): + netifs = conf.keys() + netifs.sort(lambda l, r: int(l[3:]) - int(r[3:])) + entries = [lentry(x, conf) for x in netifs] + + text = TextboxReflowed(54, "Select interfaces to create the bond on.") + buttons = ButtonBar(tui.screen, [('Create', 'create'), ('Back', 'back')]) + scroll, _ = snackutil.scrollHeight(3, len(entries)) + cbt = CheckboxTree(3, scroll) + for (c_text, c_item) in entries: + cbt.append(c_text, c_item, False) + gf = GridFormHelp(tui.screen, 'LACP Bond', '', 1, 4) + gf.add(text, 0, 0, padding=(0, 0, 0, 1)) + gf.add(cbt, 0, 1, padding=(0, 0, 0, 1)) + gf.add(buttons, 0, 3, growx=1) + gf.addHotKey('F5') + + tui.update_help_line([None, " more info"]) + loop = True + while loop: + rc = gf.run() + if rc == 'F5': + iface_details(cbt.getCurrent(), conf) + else: + loop = False + tui.screen.popWindow() + tui.screen.popHelpLine() + + button = buttons.buttonPressed(rc) + if button == 'create': + selected = cbt.getSelection() + txt = 'The bond will be created with members: %s when you activate "Ok"' % str(selected) + title = 'LACP bond creation' + confirmation = snackutil.ButtonChoiceWindowEx(tui.screen, title, txt, ('Ok', 'Cancel'), 40, default=1) + if confirmation == 'ok': + return RIGHT_FORWARDS, ('bond0', selected) + return REPEAT_STEP, None + def select_netif(text, conf, offer_existing=False, default=None): """ Display a screen that displays a choice of network interfaces to the user, with 'text' as the informative text as the data, and conf being the @@ -160,37 +224,14 @@ def select_netif(text, conf, offer_existing=False, default=None): default = iface break - def lentry(iface): - key = iface - tag = netutil.linkUp(iface) and ' ' or ' [no link]' - text = "%s (%s)%s" % (iface, conf[iface].hwaddr, tag) - return (text, key) - - def iface_details(context): - tui.update_help_line([' ', ' ']) - if context: - nic = conf[context] - - table = [ ("Name:", nic.name), - ("Driver:", nic.driver), - ("MAC Address:", nic.hwaddr), - ("PCI Details:", nic.pci_string) ] - if nic.smbioslabel != "": - table.append(("BIOS Label:", nic.smbioslabel)) - - snackutil.TableDialog(tui.screen, "Interface Details", *table) - else: - netifs_all = netutil.getNetifList(include_vlan=True) - details = map(lambda x: (x, netutil.ipaddr(x)), filter(netutil.interfaceUp, netifs_all)) - snackutil.TableDialog(tui.screen, "Networking Details", *details) - tui.screen.popHelpLine() - return True + def iface_details_with_conf(context): + return iface_details(context, conf) def update(listbox): old = listbox.current() for item in listbox.item2key.keys(): if item: - text, _ = lentry(item) + text, _ = lentry(item, conf) listbox.replace(text, item) listbox.setCurrent(old) return True @@ -203,16 +244,18 @@ def update(listbox): else: netif_list = [] if default: - def_iface = lentry(default) - netif_list += [lentry(x) for x in netifs] + def_iface = lentry(default, conf) + netif_list += [lentry(x, conf) for x in netifs] scroll, height = snackutil.scrollHeight(6, len(netif_list)) rc, entry = snackutil.ListboxChoiceWindowEx(tui.screen, "Networking", text, netif_list, - ['Ok', 'Back'], 45, scroll, height, def_iface, help='selif:info', - hotkeys={'F5': iface_details}, timeout_ms=5000, timeout_cb=update) + ['Ok', 'Create LACP Bond', 'Back'], 45, scroll, height, def_iface, help='selif:info', + hotkeys={'F5': iface_details_with_conf}, timeout_ms=5000, timeout_cb=update) tui.screen.popHelpLine() if rc == 'back': return LEFT_BACKWARDS, None + if rc == 'create lacp bond': return lacp_bond_ui(conf) + return RIGHT_FORWARDS, entry def requireNetworking(answers, defaults=None, msg=None, keys=['net-admin-interface', 'net-admin-configuration']): From bbe317ef43596f03540d205fa7a962b58fa88729 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Mon, 1 Mar 2021 10:07:17 +0100 Subject: [PATCH 2/2] fixes --- backend.py | 4 ++-- netinterface.py | 5 ++++- tui/installer/screens.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend.py b/backend.py index afbfb5c..9d39764 100644 --- a/backend.py +++ b/backend.py @@ -1637,8 +1637,8 @@ def configureNetworking(mounts, admin_iface, admin_bridge, admin_config, hn_conf if admin_config.vlan: print >>mc, "VLAN='%d'" % admin_config.vlan if admin_config.bond_mode is not None: - print >>mc, "BOND_MODE='%d'" % admin_config.bond_mode - print >>mc, "BOND_MEMBERS='%d'" % str(admin_config.bond_members) + print >>mc, "BOND_MODE='%s'" % admin_config.bond_mode + print >>mc, "BOND_MEMBERS='%s'" % ','.join(admin_config.bond_members) mc.close() diff --git a/netinterface.py b/netinterface.py index 48ffdec..f626866 100644 --- a/netinterface.py +++ b/netinterface.py @@ -70,6 +70,8 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, self.ipv6_gateway = None if bond_mode is not None: + # Not `balance-slb` because it's openvswitch specific + assert bond_mode in ["lacp", "active-backup"] assert bond_members is not None self.bond_mode = bond_mode self.bond_members = bond_members @@ -200,9 +202,10 @@ def writeBondMaster(): f.write("Type=Bond\n") f.write("NOZEROCONF=yes\n") f.write("BONDING_MASTER=yes\n") - # TODO: more mode if self.bond_mode == "lacp": f.write("BONDING_OPTS=\"mode=4 miimon=100\"\n") + elif self.bond_mode == "active-backup": + f.write("BONDING_OPTS=\"mode=1 miimon=100\"\n") if self.vlan: f.write("BOOTPROTO=none\n") diff --git a/tui/installer/screens.py b/tui/installer/screens.py index 925fce8..92bd897 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -236,6 +236,7 @@ def get_admin_interface_configuration(answers): nic.name = net_admin_interface[0] nic.bond_members = members nic.bond_mode = 'lacp' + answers['net-admin-interface'] = net_admin_interface[0] else: nic = answers['network-hardware'][net_admin_interface]