diff --git a/answerfile.py b/answerfile.py index b416b0ae..d0eca714 100644 --- a/answerfile.py +++ b/answerfile.py @@ -138,6 +138,7 @@ def parseFreshInstall(self): results.update(self.parseAssembleRaid()) results.update(self.parseRaid()) results.update(self.parseDisks()) + results.update(self.parseBonding()) results.update(self.parseInterface()) results.update(self.parseRootPassword()) results.update(self.parseNSConfig()) @@ -391,6 +392,35 @@ def parseFCoEInterface(self): return results + def parseBonding(self): + results = {} + nethw = netutil.scanConfiguration() + + nodes = getElementsByTagName(self.top_node, ['bonding']) + if len(nodes) > 1: + raise AnswerfileException(" must appear only once") + if nodes: + node = nodes[0] + bond_name = getStrAttribute(node, ['name'], mandatory=True) + if bond_name != "bond0": + raise AnswerfileException(" name must be 'bond0'") + + bond_mode = getStrAttribute(node, ['mode'], mandatory=True) + if bond_mode != "lacp": + raise AnswerfileException(" mode must be 'lacp'") + + bond_members_str = getStrAttribute(node, ['members'], mandatory=True) + bond_members = bond_members_str.split(",") + if len(bond_members) < 2: + raise AnswerfileException(" members must be at least two") + for member in bond_members: + if member not in nethw: + raise AnswerfileException(" member %r not in detected NICs" % (member,)) + + results['bonding'] = dict(name=bond_name, mode=bond_mode, members=bond_members) + + return results + def parseInterface(self): results = {} node = getElementsByTagName(self.top_node, ['admin-interface'], mandatory=True)[0] diff --git a/backend.py b/backend.py index 32e2efa5..686008f1 100644 --- a/backend.py +++ b/backend.py @@ -1542,6 +1542,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 network_backend == constants.NETWORK_BACKEND_VSWITCH: diff --git a/doc/answerfile.txt b/doc/answerfile.txt index 87aba05d..1164b9d6 100644 --- a/doc/answerfile.txt +++ b/doc/answerfile.txt @@ -230,6 +230,14 @@ Common Elements Specifies additional devices to be included in the local SR. + + + Join given ethernet interfaces using LACP bonding. Name of + resulting interface must be "bond0". It can be then be used with + + + + | Specifies the initial management interface. diff --git a/doc/parameters.txt b/doc/parameters.txt index 2f20e94b..4a75c4fd 100644 --- a/doc/parameters.txt +++ b/doc/parameters.txt @@ -122,29 +122,37 @@ Installer Proceed with installation/upgrade. - --answerfile=ans + --answerfile=url Read answerfile and perform a non-interactive installation reporting status using screens. - --rt_answerfile=ans + --rt_answerfile=url Read answerfile and perform a non-interactive installation reporting status to the console as text. - --answerfile_generator=script + --answerfile_generator=url Retrieve script, run it and use the output of it as an answerfile. - --answerfile_device[D]=eth|mac|all | --network_device=eth|mac|all + --network_device=eth|mac|lacp:members=eth,eth[,eth]*|all + --answerfile_device[D]=... Bring up networking on the given interface to allow access to answerfiles. - Default: all + Specification of LACP bonding configures a bond0 interfaces with + given members. + + Default: "all" if a non-local URL is specified for any of the above + answerfile parameters, else no device is started + + --answerfile_device is a deprecated alias, and accepts the same + parameters as --network_device. --map_netdev=eth:d|s:mac|pci[[index]]|ppn diff --git a/init b/init index b995935b..9df6c3a3 100755 --- a/init +++ b/init @@ -51,15 +51,26 @@ def configureNetworking(ui, device, config): nethw = netutil.scanConfiguration() netcfg = {} - for i in nethw: - if (device == i or device == nethw[i].hwaddr) and mode == 'static': - netcfg[i] = NetInterface(NetInterface.Static, nethw[i].hwaddr, - config_dict['ip'], config_dict['netmask'], - config_dict['gateway'], config_dict['dns'], - config_dict['domain'], config_dict['vlan']) + + if device.startswith("lacp:"): + bond_mode, bond_config = device.split(":", 1) + device = "bond0" + assert bond_config.startswith("members=") + k, v = bond_config.split("=", 1) + bond_members = v.split(",") + netutil.configure_bonding_interface(nethw, device, "lacp", bond_members) + + for devname, nic in nethw.items(): + if (device == devname or device == nic.hwaddr) and mode == 'static': + netcfg[devname] = NetInterface(NetInterface.Static, nic.hwaddr, + config_dict['ip'], config_dict['netmask'], + config_dict['gateway'], config_dict['dns'], + config_dict['domain'], config_dict['vlan'], + bond_mode=nic.bond_mode, bond_members=nic.bond_members) else: - netcfg[i] = NetInterface(NetInterface.DHCP, nethw[i].hwaddr, - vlan=config_dict['vlan']) + netcfg[devname] = NetInterface(NetInterface.DHCP, nic.hwaddr, + vlan=config_dict['vlan'], + bond_mode=nic.bond_mode, bond_members=nic.bond_members) netutil.writeNetInterfaceFiles(netcfg) netutil.writeResolverFile(netcfg, '/etc/resolv.conf') @@ -67,7 +78,7 @@ def configureNetworking(ui, device, config): iface_to_start = [] if device == 'all': iface_to_start.extend(netcfg.keys()) - elif device.startswith('eth'): + elif device.startswith('eth') or device.startswith('bond'): if device in nethw: iface_to_start.append(device) else: diff --git a/netinterface.py b/netinterface.py index 0cdcad47..01c56f36 100644 --- a/netinterface.py +++ b/netinterface.py @@ -24,7 +24,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 @@ -61,6 +61,13 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, self.ipv6addr = None self.ipv6_gateway = None + self.bond_mode = bond_mode + 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_members = bond_members + def __repr__(self): hw = "hwaddr = '%s' " % self.hwaddr @@ -83,7 +90,7 @@ def __repr__(self): ipv6 = "autoconf" else: ipv6 = "None" - vlan = ("vlan = '%d' " % self.vlan) if self.vlan else "" + vlan = (" vlan='%d' " % self.vlan) if self.vlan else "" return "" % (hw, vlan, ipv4, ipv6) @@ -164,30 +171,76 @@ 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. """ + + with open('/etc/sysconfig/network-scripts/ifcfg-%s' % member, 'w') as f: + 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") + + def writeBondMaster(): + """ Write a RedHat-style configuration entry for a bond master. """ + + with open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface, 'w') as f: + 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: + writeIpConfig(f) + + def writeIface(iface_name): + with open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface_name, 'w') as f: + f.write("NAME=%s\n" % iface_name) + f.write("DEVICE=%s\n" % iface_name) + f.write("ONBOOT=yes\n") + writeIpConfig(f) + if self.vlan: + f.write("VLAN=yes\n") + + def writeIpConfig(f): + 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) + 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") + if self.bond_mode: + # configuration of the bond interface + for idx, member in enumerate(self.bond_members): + writeBondMember(idx, member) + writeBondMaster() # ... includes IP config if not using VLAN ... + if self.vlan: + writeIface(iface_vlan) # ... but here when using VLAN 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() - + writeIface(iface_vlan) def waitUntilUp(self, iface): if not self.isStatic(): diff --git a/netutil.py b/netutil.py index 898fe0ea..d82b6b9b 100644 --- a/netutil.py +++ b/netutil.py @@ -20,9 +20,14 @@ 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", "") + # those labels cannot come from biosdevname, but are added + # here to avoid adding another API + self.bond_mode = nic_dict.get("Bond mode", None) + self.bond_members = nic_dict.get("Bond members", None) def __repr__(self): - return "" % (self.name, self.hwaddr) + return "" % (self.name, self.hwaddr, + (", bonding=" + self.bond_mode) if self.bond_mode else "") def scanConfiguration(): """ Returns a dictionary of string -> NIC with a snapshot of the NIC @@ -35,31 +40,67 @@ def scanConfiguration(): """ conf = {} nics = [] + slave_nics = [] for nif in getNetifList(): if nif not in diskutil.ibft_reserved_nics: nics.append(nif) + # identify any LACP interface previously configured (commandline) + SYSVIRTUALNET = "/sys/devices/virtual/net" + virtual_devs = os.listdir(SYSVIRTUALNET) + for iface in virtual_devs: + mode_file = os.path.join(SYSVIRTUALNET, iface, "bonding/mode") + if not os.path.exists(mode_file): + logger.log("scanConfiguration: no file %r" % mode_file) + continue # not a bonding iface + with open(mode_file) as fd: + lines = fd.readlines() + assert len(lines) == 1 + if lines[0].strip() != "802.3ad 4": + logger.log("scanConfiguration: wrong bonding mode %r != '802.3ad 4'" % lines[0]) + continue # not a LACP iface + with open(os.path.join(SYSVIRTUALNET, iface, "bonding/slaves")) as fd: + lines = fd.readlines() + assert len(lines) == 1 + bond_members = tuple(lines[0].strip().split()) + with open(os.path.join(SYSVIRTUALNET, iface, "address")) as fd: + lines = fd.readlines() + assert len(lines) == 1 + mac = lines[0].strip() + slave_nics.extend(bond_members) + conf[iface] = NIC({"Kernel name": iface, + "Assigned MAC": mac, + "Bond mode": "lacp", + "Bond members": bond_members, + }) + logger.log("scanConfiguration: bonding interfaces: {}, slaves: {}".format(conf.keys(), slave_nics)) + for nic in all_devices_all_names().values(): name = nic.get("Kernel name", "") - if name in nics: - conf[name] = NIC(nic) + if name not in nics: + logger.log("scanConfiguration: {} not in nics".format(name)) + continue + if name in slave_nics: + logger.log("scanConfiguration: {} in slave_nics".format(name)) + continue + conf[name] = NIC(nic) return conf +def netifSortKey(interface): + m = re.match(r"([a-z]+)(\d+)((?:\..+)?)", interface) + return m.groups() + def getNetifList(include_vlan=False): all = os.listdir("/sys/class/net") def ethfilter(interface, include_vlan): - return interface.startswith("eth") and (interface.isalnum() or - (include_vlan and "." in interface)) - - def rankValue(ethx): - iface, vlan = splitInterfaceVlan(ethx) - return (int(iface.strip('eth'))*10000 + (int(vlan) if vlan else -1)) + return ((interface.startswith("eth") or interface.startswith("bond")) and + (interface.isalnum() or (include_vlan and "." in interface))) relevant = filter(lambda x: ethfilter(x, include_vlan), all) - relevant.sort(lambda l, r: rankValue(l) - rankValue(r)) + relevant.sort(key=netifSortKey) return relevant # writes an 'interfaces' style file given a network configuration object list @@ -104,16 +145,14 @@ def writeResolverFile(configuration, filename): interface_up = {} # simple wrapper for calling the local ifup script: -def splitInterfaceVlan(interface): +def splitInterfaceVlan(interface_vlan): if "." in interface: return interface.split(".", 1) return interface, None -def ifup(interface): - device, vlan = splitInterfaceVlan(interface) - assert device in getNetifList() - interface_up[interface] = True - return util.runCmd2(['ifup', interface]) +def ifup(interface_vlan): + interface_up[interface_vlan] = True + return util.runCmd2(['ifup', interface_vlan]) def ifdown(interface): if interface in interface_up: @@ -169,8 +208,8 @@ def networkingUp(): return False # make a string to help users identify a network interface: -def getPCIInfo(interface): - interface, vlan = splitInterfaceVlan(interface) +def getPCIInfo(interface_vlan): + interface, vlan = splitInterfaceVlan(interface_vlan) info = "" devpath = os.path.realpath('/sys/class/net/%s/device' % interface) slot = devpath[len(devpath) - 7:] @@ -192,8 +231,8 @@ def getPCIInfo(interface): return info -def getDriver(interface): - interface, vlan = splitInterfaceVlan(interface) +def getDriver(interface_vlan): + interface, vlan = splitInterfaceVlan(interface_vlan) return os.path.basename(os.path.realpath('/sys/class/net/%s/device/driver' % interface)) def __readOneLineFile__(filename): @@ -276,6 +315,23 @@ def as_xml(self): output += '\n' return output +def configure_bonding_interface(nethw, bond_iface, bond_mode, bond_members): + assert len(bond_members) >= 2 + for devname in bond_members: + assert devname in nethw + + # forge a NIC definition for the LACP interface + ref_devname = bond_members[0] + nethw[bond_iface] = NIC({"Kernel name": bond_iface, + "Assigned MAC": nethw[ref_devname].hwaddr, + "Bond mode": bond_mode, + "Bond members": bond_members, + }) + + # remove LACP member NIC definitions from the list + for devname in bond_members: + del nethw[devname] + ### EA-1069 import xcp.logger as LOG diff --git a/tui/fcoe.py b/tui/fcoe.py index 5957ec85..f619ea99 100644 --- a/tui/fcoe.py +++ b/tui/fcoe.py @@ -34,7 +34,7 @@ def select_fcoe_ifaces(answers): return - netifs.sort(lambda l, r: int(l[3:]) - int(r[3:])) + netifs.sort(key=netutil.netifSortKey) def iface_details(context): tui.update_help_line([' ', ' ']) diff --git a/tui/installer/__init__.py b/tui/installer/__init__.py index 366bcb92..56a8a526 100644 --- a/tui/installer/__init__.py +++ b/tui/installer/__init__.py @@ -46,8 +46,6 @@ def upgrade_but_no_settings_predicate(answers): ('installation-to-overwrite' not in answers or \ not answers['installation-to-overwrite'].settingsAvailable()) - has_multiple_nics = lambda a: len(a['network-hardware'].keys()) > 1 - is_reinstall_fn = lambda a: a['install-type'] == constants.INSTALL_TYPE_REINSTALL is_clean_install_fn = lambda a: a['install-type'] == constants.INSTALL_TYPE_FRESH is_not_restore_fn = lambda a: a['install-type'] != constants.INSTALL_TYPE_RESTORE @@ -162,7 +160,7 @@ def out_of_order_pool_upgrade_fn(answers): Step(uis.get_root_password, predicates=[is_not_restore_fn, not_preserve_settings]), Step(uis.get_admin_interface, - predicates=[is_not_restore_fn, has_multiple_nics, not_preserve_settings]), + predicates=[is_not_restore_fn, not_preserve_settings]), Step(uis.get_admin_interface_configuration, predicates=[is_not_restore_fn, not_preserve_settings]), Step(uis.get_name_service_configuration, diff --git a/tui/installer/screens.py b/tui/installer/screens.py index 7802abc7..8a432ba8 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -224,7 +224,9 @@ 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'] + nic = answers['network-hardware'][net_admin_interface] defaults = None try: @@ -233,7 +235,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 faf87dc9..8b676fbf 100644 --- a/tui/network.py +++ b/tui/network.py @@ -126,13 +126,81 @@ 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(key=netutil.netifSortKey) + 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 = 'Create a LACP bond with members %s? This choice cannot be rolled back.' % ( + ", ".join(selected)) + title = 'LACP bond creation' + confirmation = snackutil.ButtonChoiceWindowEx(tui.screen, title, txt, + ('Ok', 'Cancel'), 40, default=1) + if confirmation == 'ok': + netutil.configure_bonding_interface(conf, 'bond0', "lacp", tuple(selected)) + + # always back to iface selection + 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 @@ -140,7 +208,7 @@ def select_netif(text, conf, offer_existing=False, default=None): """ netifs = conf.keys() - netifs.sort(lambda l, r: int(l[3:]) - int(r[3:])) + netifs.sort(key=netutil.netifSortKey) if default not in netifs: # find first link that is up @@ -150,37 +218,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 @@ -193,16 +238,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']): @@ -248,6 +295,8 @@ def specify_configuration(answers, txt, defaults): if 'reuse-networking' in answers and answers['reuse-networking']: return RIGHT_FORWARDS + logger.log("answers['interface'] = {!r}".format(answers['interface'])) + # FIXME if this is a tuple we have a bond, what to do ? direction, conf = get_iface_configuration(nethw[answers['interface']], txt, defaults=defaults, include_dns=True) if direction == RIGHT_FORWARDS: @@ -262,13 +311,8 @@ def specify_configuration(answers, txt, defaults): def_iface = defaults[interface_key] if config_key in defaults: def_conf = defaults[config_key] - if len(nethw.keys()) > 1 or netutil.networkingUp(): - seq = [ uicontroller.Step(select_interface, args=[def_iface, msg]), - uicontroller.Step(specify_configuration, args=[None, def_conf]) ] - else: - text = "%s Setup needs network access to continue.\n\nHow should networking be configured at this time?" % (version.PRODUCT_BRAND or version.PLATFORM_NAME) - conf_dict['interface'] = nethw.keys()[0] - seq = [ uicontroller.Step(specify_configuration, args=[text, def_conf]) ] + seq = [ uicontroller.Step(select_interface, args=[def_iface, msg]), + uicontroller.Step(specify_configuration, args=[None, def_conf]) ] direction = uicontroller.runSequence(seq, conf_dict) if direction == RIGHT_FORWARDS and 'config' in conf_dict: