Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WiP] add possibility to configure lacp bonding at install #25

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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='%s'" % admin_config.bond_mode
print >>mc, "BOND_MEMBERS='%s'" % ','.join(admin_config.bond_members)

mc.close()

if preserve_settings:
Expand Down
108 changes: 87 additions & 21 deletions netinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +69,13 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None,
self.ipv6addr = 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"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the users provided a different value? How will the error be displayed to them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk, i've reproduced how such errors are handled in the code.
The UI won't allow to give other values. The answerfiles think will be handled in answerfile.py so this is not where to treat user error display.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this means you'll sanitize answerfile input so that those asserts are never reached?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, once i'm tackling this part in the parsong of the answerfile.

assert bond_members is not None
self.bond_mode = bond_mode
self.bond_members = bond_members

def __repr__(self):
hw = "hwaddr = '%s' " % self.hwaddr

Expand Down Expand Up @@ -172,30 +179,89 @@ 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")
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")
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():
Expand Down
2 changes: 2 additions & 0 deletions netutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<NIC: %s (%s)>" % (self.name, self.hwaddr)
Expand Down
15 changes: 13 additions & 2 deletions tui/installer/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,18 @@ 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'
answers['net-admin-interface'] = net_admin_interface[0]
else:
nic = answers['network-hardware'][net_admin_interface]

defaults = None
try:
Expand All @@ -236,7 +247,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

Expand Down
107 changes: 75 additions & 32 deletions tui/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<F5> 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
Expand All @@ -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
Expand All @@ -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']):
Expand Down