#!/usr/bin/env python import urwid import urwid.raw_display import urwid.web_display import logging import sys import re import copy import socket import struct import netaddr import dhcp_checker.api import netifaces import subprocess from fuelmenu.settings import * from fuelmenu.common import network, puppet, replace, nailyfactersettings, \ dialog from fuelmenu.common.urwidwrapper import * log = logging.getLogger('fuelmenu.pxe_setup') blank = urwid.Divider() #Need to define fields in order so it will render correctly #fields = ["hostname", "domain", "mgmt_if","dhcp_start","dhcp_end", # "blank","ext_if","ext_dns"] fields = ["static_label", "ADMIN_NETWORK/static_start", "ADMIN_NETWORK/static_end", "blank", "dynamic_label", "ADMIN_NETWORK/first", "ADMIN_NETWORK/last"] facter_translate = { "ADMIN_NETWORK/interface": "internal_interface", "ADMIN_NETWORK/ipaddr": "internal_ipaddress", "ADMIN_NETWORK/netmask": "internal_netmask", "ADMIN_NETWORK/first": "dhcp_pool_start", "ADMIN_NETWORK/last": "dhcp_pool_end", "ADMIN_NETWORK/static_start": "static_pool_start", "ADMIN_NETWORK/static_end": "static_pool_end", } mnbs_internal_ipaddress = "10.20.0.2" mnbs_internal_netmask = "255.255.255.0" mnbs_static_pool_start = "10.20.0.130" mnbs_static_pool_end = "10.20.0.250" mnbs_dhcp_pool_start = "10.20.0.10" mnbs_dhcp_pool_end = "10.20.0.120" mnbs_internal_interface = "eth1" DEFAULTS = { #"ADMIN_NETWORK/interface" : { "label" : "Management Interface", # "tooltip": "This is the INTERNAL network for provisioning", # "value" : "eth0"}, "ADMIN_NETWORK/first": {"label": "DHCP Pool Start", "tooltip": "Used for defining IPs for hosts and \ instance public addresses", "value": "10.0.0.130"}, "ADMIN_NETWORK/last": {"label": "DHCP Pool End", "tooltip": "Used for defining IPs for hosts and \ instance public addresses", "value": "10.0.0.254"}, "static_label": {"label": "Static pool for installed nodes:", "tooltip": "", "value": "label"}, "ADMIN_NETWORK/static_start": {"label": "Static Pool Start", "tooltip": "Static pool for installed \ nodes", "value": "10.0.0.10"}, "ADMIN_NETWORK/static_end": {"label": "Static Pool End", "tooltip": "Static pool for installed nodes", "value": "10.0.0.120"}, "dynamic_label": {"label": "DHCP pool for node discovery:", "tooltip": "", "value": "label"}, #"ADMIN_NETWORK/dynamic_start" : { "label" : "Static Pool Start", # "tooltip": "DHCP pool for node discovery", # "value" : "10.0.0.10"}, #"ADMIN_NETWORK/dynamic_end": { "label" : "Static Pool End", # "tooltip": "DHCP pool for node discovery", # "value" : "10.0.0.120"}, } class cobblerconf(urwid.WidgetWrap): def __init__(self, parent): self.name = "PXE Setup" self.priority = 20 self.visible = True self.netsettings = dict() self.parent = parent self.deployment = "pre" self.getNetwork() self.gateway = self.get_default_gateway_linux() self.activeiface = sorted(self.netsettings.keys())[0] self.parent.managediface = self.activeiface self.extdhcp = True self.oldsettings = self.load() self.screen = None def check(self, args): """Validates all fields have valid values and some sanity checks""" self.parent.footer.set_text("Checking data...") self.parent.refreshScreen() #Get field information responses = dict() for index, fieldname in enumerate(fields): if fieldname == "blank" or "label" in fieldname: pass else: responses[fieldname] = self.edits[index].get_edit_text() ###Validate each field errors = [] #Set internal_{ipaddress,netmask,interface} responses["ADMIN_NETWORK/interface"] = self.activeiface responses["ADMIN_NETWORK/netmask"] = self.netsettings[ self.activeiface]["netmask"] responses["ADMIN_NETWORK/ipaddr"] = self.netsettings[ self.activeiface]["addr"] #ensure management interface is valid if responses["ADMIN_NETWORK/interface"] not in self.netsettings.keys(): errors.append("Management interface not valid") else: self.parent.footer.set_text("Scanning for DHCP servers. \ Please wait...") self.parent.refreshScreen() ###Start DHCP check on this interface #dhcp_server_data=[{'server_id': '192.168.200.2', 'iface': 'eth2', # 'yiaddr': '192.168.200.15', 'mac': # '52:54:00:12:35:02', 'server_ip': '192.168.200.2', # 'dport': 67, 'message': 'offer', # 'gateway': '0.0.0.0'}] dhcp_server_data = dhcp_checker.api.check_dhcp_on_eth( self.activeiface, timeout=5) num_dhcp = len(dhcp_server_data) if num_dhcp == 0: log.debug("No DHCP servers found") else: #Problem exists, but permit user to continue log.error("%s foreign DHCP server(s) found: %s" % (num_dhcp, dhcp_server_data)) #Build dialog elements dhcp_info = [] dhcp_info.append(urwid.Padding( urwid.Text(("header", "!!! WARNING !!!")), "center")) dhcp_info.append(TextLabel("You have selected an interface \ that contains one or more DHCP servers. This will impact provisioning. You \ should disable these DHCP servers before you continue, or else deployment \ will likely fail.")) dhcp_info.append(TextLabel("")) for index, dhcp_server in enumerate(dhcp_server_data): dhcp_info.append(TextLabel("DHCP Server #%s:" % (index+1))) dhcp_info.append(TextLabel("IP address: %-10s" % dhcp_server['server_ip'])) dhcp_info.append(TextLabel("MAC address: %-10s" % dhcp_server['mac'])) dhcp_info.append(TextLabel("")) dialog.display_dialog(self, urwid.Pile(dhcp_info), "DHCP Servers Found on %s" % self.activeiface) ###Ensure pool start and end are on the same subnet as mgmt_if #Ensure mgmt_if has an IP first if len(self.netsettings[responses["ADMIN_NETWORK/interface"]][ "addr"]) == 0: errors.append("Go to Interfaces to configure management \ interface first.") else: #Ensure ADMIN_NETWORK/interface is not running DHCP if self.netsettings[responses["ADMIN_NETWORK/interface"]][ "bootproto"] == "dhcp": errors.append("%s is running DHCP.Change it to static " "first." % self.activeiface) #Ensure Static Pool Start and Static Pool are valid IPs try: if netaddr.valid_ipv4(responses[ "ADMIN_NETWORK/static_start"]): static_start = netaddr.IPAddress(responses[ "ADMIN_NETWORK/static_start"]) else: raise Exception("") except Exception, e: errors.append("Not a valid IP address for Static Pool " "Start: %s" % e) try: if netaddr.valid_ipv4(responses[ "ADMIN_NETWORK/static_end"]): static_end = netaddr.IPAddress(responses[ "ADMIN_NETWORK/static_end"]) else: raise Exception("") except: errors.append("Invalid IP address for Static Pool end: " "%s" % responses["ADMIN_NETWORK/static_end"]) #Ensure DHCP Pool Start and DHCP Pool are valid IPs try: if netaddr.valid_ipv4(responses["ADMIN_NETWORK/first"]): dhcp_start = netaddr.IPAddress( responses["ADMIN_NETWORK/first"]) else: raise Exception("") except Exception, e: errors.append("Invalid IP address for DHCP Pool Start:" " %s" % e) try: if netaddr.valid_ipv4(responses["ADMIN_NETWORK/last"]): dhcp_end = netaddr.IPAddress( responses["ADMIN_NETWORK/last"]) else: raise Exception("") except: errors.append("Invalid IP address for DHCP Pool end: %s" % responses["ADMIN_NETWORK/last"]) #Ensure pool start and end are in the same subnet of each other netmask = self.netsettings[responses["ADMIN_NETWORK/interface"] ]["netmask"] if network.inSameSubnet(responses["ADMIN_NETWORK/first"], responses["ADMIN_NETWORK/last"], netmask) is False: errors.append("DHCP Pool start and end are not in the " "same subnet.") #Ensure pool start and end are in the right netmask mgmt_if_ipaddr = self.netsettings[responses[ "ADMIN_NETWORK/interface"]]["addr"] if network.inSameSubnet(responses["ADMIN_NETWORK/first"], mgmt_if_ipaddr, netmask) is False: errors.append("DHCP Pool start does not match management" " network.") if network.inSameSubnet(responses["ADMIN_NETWORK/last"], mgmt_if_ipaddr, netmask) is False: errors.append("DHCP Pool end is not in the same subnet as" " management interface.") if len(errors) > 0: self.parent.footer.set_text("Errors: %s First error: %s" % (len(errors), errors[0])) return False else: self.parent.footer.set_text("No errors found.") return responses def apply(self, args): responses = self.check(args) if responses is False: log.error("Check failed. Not applying") log.error("%s" % (responses)) return False #Always save even if "post" self.save(responses) #Need to decide if we are pre-deployment or post-deployment if self.deployment == "post": self.updateCobbler(responses) def updateCobbler(self, params): patterns = { 'cblr_server': '^server: .*', 'cblr_next_server': '^next_server: .*', 'mgmt_if': '^interface=.*', 'domain': '^domain=.*', 'server': '^server=.*', 'dhcp-range': '^dhcp-range=', 'dhcp-option': '^dhcp-option=', 'pxe-service': '^pxe-service=(^,)', 'dhcp-boot': '^dhcp-boot=([^,],{3}),' } def cancel(self, button): for index, fieldname in enumerate(fields): if fieldname == "blank" or "label" in fieldname: pass else: self.edits[index].set_edit_text(DEFAULTS[fieldname]['value']) self.setNetworkDetails() def load(self): #Read in yaml defaultsettings = Settings().read(self.parent.defaultsettingsfile) oldsettings = defaultsettings oldsettings.update(Settings().read(self.parent.settingsfile)) log.debug("Old settings %s" % oldsettings) for setting in DEFAULTS.keys(): if "label" in setting: continue elif "/" in setting: part1, part2 = setting.split("/") DEFAULTS[setting]["value"] = oldsettings[part1][part2] else: DEFAULTS[setting]["value"] = oldsettings[setting] if oldsettings["ADMIN_NETWORK"]["interface"] \ in self.netsettings.keys(): self.activeiface = oldsettings["ADMIN_NETWORK"]["interface"] return oldsettings def save(self, responses): ## Generic settings start ## newsettings = dict() for setting in responses.keys(): if "/" in setting: part1, part2 = setting.split("/") if part1 not in newsettings: #We may not touch all settings, so copy oldsettings first newsettings[part1] = self.oldsettings[part1] newsettings[part1][part2] = responses[setting] else: newsettings[setting] = responses[setting] ## Generic settings end ## ## Need to calculate and set cidr, netmask, size newsettings['ADMIN_NETWORK']['netmask'] = self.netsettings[newsettings ['ADMIN_NETWORK']['interface']]["netmask"] newsettings['ADMIN_NETWORK']['cidr'] = network.getCidr( self.netsettings[newsettings['ADMIN_NETWORK']['interface']][ "addr"], newsettings['ADMIN_NETWORK']['netmask']) newsettings['ADMIN_NETWORK']['size'] = network.getCidrSize( newsettings['ADMIN_NETWORK']['cidr']) log.debug(str(newsettings)) Settings().write(newsettings, defaultsfile=self.parent.settingsfile, outfn="newsettings.yaml") #Write naily.facts factsettings = dict() #for key in newsettings.keys(): log.debug(str(facter_translate)) log.debug(str(newsettings)) for key in facter_translate.keys(): factsettings[facter_translate[key]] = responses[key] n = nailyfactersettings.NailyFacterSettings() log.debug("Facts to write: %s" % factsettings) n.write(factsettings) #Set oldsettings to reflect new settings self.oldsettings = newsettings #Update DEFAULTS for index, fieldname in enumerate(fields): if fieldname != "blank" and "label" not in fieldname: DEFAULTS[fieldname]['value'] = responses[fieldname] self.parent.footer.set_text("Changes saved successfully.") def getNetwork(self): """Uses netifaces module to get addr, broadcast, netmask about network interfaces""" for iface in netifaces.interfaces(): if 'lo' in iface or 'vir' in iface: #if 'lo' in iface or 'vir' in iface or 'vbox' in iface: if iface != "virbr2-nic": continue try: self.netsettings.update({iface: netifaces.ifaddresses(iface)[ netifaces.AF_INET][0]}) self.netsettings[iface]["onboot"] = "Yes" except: self.netsettings.update({iface: {"addr": "", "netmask": "", "onboot": "no"}}) self.netsettings[iface]['mac'] = netifaces.ifaddresses(iface)[ netifaces.AF_LINK][0]['addr'] #Set link state try: with open("/sys/class/net/%s/operstate" % iface) as f: content = f.readlines() self.netsettings[iface]["link"] = content[0].strip() except: self.netsettings[iface]["link"] = "unknown" #Change unknown link state to up if interface has an IP if self.netsettings[iface]["link"] == "unknown": if self.netsettings[iface]["addr"] != "": self.netsettings[iface]["link"] = "up" #Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV self.netsettings[iface]['bootproto'] = "none" try: with open("/etc/sysconfig/network-scripts/ifcfg-%s" % iface)\ as fh: for line in fh: if re.match("^BOOTPROTO=", line): self.netsettings[iface]['bootproto'] = \ line.split('=').strip() break except: #Check for dhclient process running for this interface if self.getDHCP(iface): self.netsettings[iface]['bootproto'] = "dhcp" else: self.netsettings[iface]['bootproto'] = "none" self.gateway = self.get_default_gateway_linux() def getDHCP(self, iface): """Returns True if the interface has a dhclient process running""" noout = open('/dev/null', 'w') dhclient_running = subprocess.call(["pgrep", "-f", "dhclient.*%s" % (iface)], stdout=noout, stderr=noout) if dhclient_running == 0: return True else: return False def get_default_gateway_linux(self): """Read the default gateway directly from /proc.""" with open("/proc/net/route") as fh: for line in fh: fields = line.strip().split() if fields[1] != '00000000' or not int(fields[3], 16) & 2: continue return socket.inet_ntoa(struct.pack("