fuel-menu/fuelmenu/modules/interfaces.py

497 lines
21 KiB
Python

#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import copy
import socket
import struct
import re
import netaddr
import netifaces
import dhcp_checker.api
from fuelmenu.settings import *
from fuelmenu.common import network, puppet, replace, dialog
from fuelmenu.common.urwidwrapper import *
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
fields = ["blank", "ifname", "onboot", "bootproto", "ipaddr", "netmask",
"gateway"]
DEFAULTS = {
"ifname": {"label": "Interface name:",
"tooltip": "Interface system identifier",
"value": "locked"},
"onboot": {"label": "Enabled on boot:",
"tooltip": "",
"value": "radio"},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"value": "radio",
"choices": ["DHCP", "Static"]},
"ipaddr": {"label": "IP address:",
"tooltip": "Manual IP address (example 192.168.1.2)",
"value": ""},
"netmask": {"label": "Netmask:",
"tooltip": "Manual netmask (example 255.255.255.0)",
"value": "255.255.255.0"},
"gateway": {"label": "Default Gateway:",
"tooltip": "Manual gateway to access Internet (example "
"192.168.1.1)",
"value": ""},
}
YAMLTREE = "cobbler_common"
class interfaces(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Network Setup"
self.priority = 5
self.visible = True
self.netsettings = dict()
self.parent = parent
self.screen = None
self.log = logging
self.log.basicConfig(filename='./fuelmenu.log', level=logging.DEBUG)
self.log.info("init Interfaces")
self.getNetwork()
self.gateway = self.get_default_gateway_linux()
self.activeiface = sorted(self.netsettings.keys())[0]
self.extdhcp = True
def fixDnsmasqUpstream(self):
#check upstream dns server
with open('/etc/dnsmasq.upstream', 'r') as f:
dnslines = f.readlines()
f.close()
nameservers = dnslines[0].split(" ")[1:]
for nameserver in nameservers:
if not self.checkDNS(nameserver):
nameservers.remove(nameserver)
if nameservers == []:
#Write dnsmasq upstream server to default if it's not readable
with open('/etc/dnsmasq.upstream', 'w') as f:
nameservers = DEFAULTS['DNS_UPSTREAM']['value'].replace(
',', ' ')
f.write("nameserver %s\n" % nameservers)
f.close()
def fixEtcHosts(self):
#replace ip for env variable HOSTNAME in /etc/hosts
if self.netsettings[self.parent.managediface]["addr"] != "":
managediface_ip = self.netsettings[self.parent.managediface][
"addr"]
else:
managediface_ip = "127.0.0.1"
found = False
with open("/etc/hosts") as fh:
for line in fh:
if re.match("%s.*%s" % (managediface_ip, socket.gethostname()),
line):
found = True
break
if not found:
expr = ".*%s.*" % socket.gethostname()
replace.replaceInFile("/etc/hosts", expr, "%s %s" % (
managediface_ip, socket.gethostname()))
def check(self, args):
"""Validate that all fields have valid values and some sanity checks"""
#Get field information
responses = dict()
self.parent.footer.set_text("Checking data...")
for index, fieldname in enumerate(fields):
if fieldname == "blank" or fieldname == "ifname":
pass
elif fieldname == "bootproto":
rb_group = self.edits[index].rb_group
if rb_group[0].state:
responses["bootproto"] = "dhcp"
else:
responses["bootproto"] = "none"
elif fieldname == "onboot":
rb_group = self.edits[index].rb_group
if rb_group[0].state:
responses["onboot"] = "yes"
else:
responses["onboot"] = "no"
else:
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
#Perform checks only if enabled
if responses["onboot"] == "no":
return responses
if responses["bootproto"] == "dhcp":
self.parent.footer.set_text("Scanning for DHCP servers. "
"Please wait...")
self.parent.refreshScreen()
try:
dhcp_server_data = dhcp_checker.api.check_dhcp_on_eth(
self.activeiface, timeout=3)
if len(dhcp_server_data) < 1:
self.log.debug("No DHCP servers found. Warning user about "
"dhcp_nowait.")
#Build dialog elements
dhcp_info = []
dhcp_info.append(urwid.Padding(
urwid.Text(("header", "!!! WARNING !!!")),
"center"))
dhcp_info.append(TextLabel("Unable to detect DHCP server on "
"interface %s." % (self.activeiface) +
"\nDHCP will be set up in the background, "
"but may not receive an IP address. You may "
"want to check your DHCP connection manually "
"using the Shell Login menu to the left."))
dialog.display_dialog(self, urwid.Pile(dhcp_info),
"DHCP Servers Found on %s"
% self.activeiface)
responses["dhcp_nowait"] = True
except:
self.log.warning("dhcp_checker failed to check on %s"
% self.activeiface)
#TODO: Fix set up DHCP on down iface
responses["dhcp_nowait"] = False
#Check ipaddr, netmask, gateway only if static
if responses["bootproto"] == "none":
try:
if netaddr.valid_ipv4(responses["ipaddr"]):
ipaddr = netaddr.IPAddress(responses["ipaddr"])
else:
raise Exception("")
except:
errors.append("Not a valid IP address: %s" %
responses["ipaddr"])
try:
if netaddr.valid_ipv4(responses["netmask"]):
netmask = netaddr.IPAddress(responses["netmask"])
if netmask.is_netmask is False:
raise Exception("")
else:
raise Exception("")
except:
errors.append("Not a valid netmask: %s" % responses["netmask"])
try:
if len(responses["gateway"]) > 0:
#Check if gateway is valid
if netaddr.valid_ipv4(responses["gateway"]) is False:
raise Exception("Gateway IP address is not valid")
#Check if gateway is in same subnet
if network.inSameSubnet(responses["ipaddr"],
responses["gateway"],
responses["netmask"]) is False:
raise Exception("Gateway IP address is not in the "
"same subnet as IP address")
except Exception, e:
errors.append(e)
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:
self.log.error("Check failed. Not applying")
self.parent.footer.set_text("Check failed. Not applying.")
self.log.error("%s" % (responses))
return False
self.parent.footer.set_text("Applying changes...")
puppetclass = "l23network::l3::ifconfig"
if responses["onboot"].lower() == "no":
params = {"ipaddr": "none"}
elif responses["bootproto"] == "dhcp":
if "dhcp_nowait" in responses.keys():
params = {"ipaddr": "dhcp",
"dhcp_nowait": responses["dhcp_nowait"]}
else:
params = {"ipaddr": "dhcp"}
else:
params = {"ipaddr": responses["ipaddr"],
"netmask": responses["netmask"],
"check_by_ping": "none"}
if len(responses["gateway"]) > 1:
params["gateway"] = responses["gateway"]
self.log.info("Puppet data: %s %s %s" % (
puppetclass, self.activeiface, params))
try:
#Gateway handling so DHCP will set gateway
if responses["bootproto"] == "dhcp":
expr = '^GATEWAY=.*'
replace.replaceInFile("/etc/sysconfig/network", expr,
"GATEWAY=")
self.parent.refreshScreen()
puppet.puppetApply(puppetclass, self.activeiface, params)
self.getNetwork()
expr = '^GATEWAY=.*'
gateway = self.get_default_gateway_linux()
if gateway is None:
gateway = ""
replace.replaceInFile("/etc/sysconfig/network", expr, "GATEWAY=%s"
% gateway)
self.fixEtcHosts()
except Exception, e:
self.log.error(e)
self.parent.footer.set_text("Error applying changes. Check logs "
"for details.")
self.getNetwork()
self.setNetworkDetails()
return False
self.parent.footer.set_text("Changes successfully applied.")
self.getNetwork()
self.setNetworkDetails()
return True
def save(self, args):
newsettings = dict()
newsettings['common'] = {YAMLTREE: {"domain": DEFAULTS['domain'][
'value']}}
for key, widget in self.edits.items():
text = widget.original_widget.get_edit_text()
newsettings['common'][YAMLTREE][key] = text
log.warning(str(newsettings))
Settings().write(newsettings, tree=YAMLTREE)
logging.warning('And this, too')
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 iface != "virbr2-nic":
continue
try:
self.netsettings.update({iface: netifaces.ifaddresses(iface)[
netifaces.AF_INET][0]})
self.netsettings[iface]["onboot"] = "Yes"
except:
#Interface is down, so mark it onboot=no
self.netsettings.update({iface: {"addr": "", "netmask": "",
"onboot": "no"}})
self.netsettings[iface]['mac'] = netifaces.ifaddresses(iface)[
netifaces.AF_LINK][0]['addr']
self.gateway = self.get_default_gateway_linux()
#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
#default to static
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"
def getDHCP(self, iface):
"""Returns True if the interface has a dhclient process running"""
import subprocess
noout = open('/dev/null', 'w')
dhclient_running = subprocess.call(["pgrep", "-f", "dhclient.*%s" %
(iface)], stdout=noout,
stderr=noout)
#self.log.info("Interface %s: %s" % (iface, dhclient_running))
if dhclient_running != 0:
return False
else:
return True
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("<L", int(fields[2], 16)))
def radioSelectIface(self, current, state, user_data=None):
"""Update network details and display information"""
### This makes no sense, but urwid returns the previous object.
### The previous object has True state, which is wrong.
### Somewhere in current.group a RadioButton is set to True.
### Our quest is to find it.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.activeiface = rb.base_widget.get_label()
break
self.getNetwork()
self.setNetworkDetails()
return
def radioSelectExtIf(self, current, state, user_data=None):
"""Update network details and display information"""
### This makes no sense, but urwid returns the previous object.
### The previous object has True state, which is wrong.
### Somewhere in current.group a RadioButton is set to True.
### Our quest is to find it.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
if rb.base_widget.get_label() == "Yes":
self.extdhcp = True
else:
self.extdhcp = False
break
return
def setNetworkDetails(self):
self.net_text1.set_text("Interface: %-13s Link: %s" % (
self.activeiface,
self.netsettings[self.activeiface]['link'].upper()))
self.net_text2.set_text("IP: %-15s MAC: %s" % (
self.netsettings[self.activeiface]['addr'],
self.netsettings[self.activeiface]['mac']))
self.net_text3.set_text("Netmask: %-15s Gateway: %s" % (
self.netsettings[self.activeiface]['netmask'],
self.gateway))
#Set text fields to current netsettings
for index, fieldname in enumerate(fields):
if fieldname == "ifname":
self.edits[index].base_widget.set_edit_text(self.activeiface)
elif fieldname == "bootproto":
rb_group = self.edits[index].rb_group
for rb in rb_group:
if self.netsettings[self.activeiface]["bootproto"].lower()\
== "dhcp":
rb_group[0].set_state(True)
rb_group[1].set_state(False)
else:
rb_group[0].set_state(False)
rb_group[1].set_state(True)
elif fieldname == "onboot":
rb_group = self.edits[index].rb_group
for rb in rb_group:
if self.netsettings[self.activeiface]["onboot"].lower()\
== "yes":
rb_group[0].set_state(True)
rb_group[1].set_state(False)
else:
#onboot should only be no if the interface is also down
if self.netsettings[self.activeiface]['addr'] == "":
rb_group[0].set_state(False)
rb_group[1].set_state(True)
else:
rb_group[0].set_state(True)
rb_group[1].set_state(False)
elif fieldname == "ipaddr":
self.edits[index].set_edit_text(self.netsettings[
self.activeiface]['addr'])
elif fieldname == "netmask":
self.edits[index].set_edit_text(self.netsettings[
self.activeiface]['netmask'])
elif fieldname == "gateway":
#Gateway is for this iface only if gateway is matches subnet
if network.inSameSubnet(
self.netsettings[self.activeiface]['addr'],
self.gateway,
self.netsettings[self.activeiface]['netmask']):
self.edits[index].set_edit_text(self.gateway)
else:
self.edits[index].set_edit_text("")
def refresh(self):
self.getNetwork()
self.setNetworkDetails()
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = TextLabel("Network interface setup")
#Current network settings
self.net_text1 = TextLabel("")
self.net_text2 = TextLabel("")
self.net_text3 = TextLabel("")
self.net_choices = ChoicesGroup(self, sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelectIface)
self.edits = []
toolbar = self.parent.footer
for key in fields:
#Example: key = hostname, label = Hostname, value = fuel
if key == "blank":
self.edits.append(blank)
elif DEFAULTS[key]["value"] == "radio":
label = TextLabel(DEFAULTS[key]["label"])
if "choices" in DEFAULTS[key]:
choices_list = DEFAULTS[key]["choices"]
else:
choices_list = ["Yes", "No"]
choices = ChoicesGroup(self, choices_list,
default_value="Yes",
fn=self.radioSelectExtIf)
columns = Columns([('weight', 2, label),
('weight', 3, choices)])
#Attach choices rb_group so we can use it later
columns.rb_group = choices.rb_group
self.edits.append(columns)
else:
caption = DEFAULTS[key]["label"]
default = DEFAULTS[key]["value"]
tooltip = DEFAULTS[key]["tooltip"]
disabled = True if key == "ifname" else False
self.edits.append(TextField(key, caption, 23, default, tooltip,
toolbar, disabled=disabled))
#Button to check
button_check = Button("Check", self.check)
#Button to apply (and check again)
button_apply = Button("Apply", self.apply)
#Wrap buttons into Columns so it doesn't expand and look ugly
check_col = Columns([button_check, button_apply, ('weight', 3, blank)])
self.listbox_content = [text1, blank]
self.listbox_content.extend([self.net_choices, self.net_text1,
self.net_text2, self.net_text3,
blank])
self.listbox_content.extend(self.edits)
self.listbox_content.append(blank)
self.listbox_content.append(check_col)
#Add everything into a ListBox and return it
self.listwalker = urwid.SimpleListWalker(self.listbox_content)
screen = urwid.ListBox(self.listwalker)
self.setNetworkDetails()
self.screen = screen
return screen