fuel-web/fuelmenu/fuelmenu/modules/interfaces.py

436 lines
19 KiB
Python

#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import dhcp_checker.api
import dhcp_checker.utils
from fuelmenu.common import dialog
from fuelmenu.common.errors import BadIPException
from fuelmenu.common.errors import NetworkException
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common import network
from fuelmenu.common import puppet
from fuelmenu.common import replace
from fuelmenu.common import timeout
import fuelmenu.common.urwidwrapper as widget
import logging
import netaddr
import re
import socket
import subprocess
import traceback
import urwid
import urwid.raw_display
import urwid.web_display
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
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
#UI text
self.net_choices = widget.ChoicesGroup(sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelectIface)
#Placeholders for network settings text
self.net_text1 = widget.TextLabel("")
self.net_text2 = widget.TextLabel("")
self.net_text3 = widget.TextLabel("")
self.header_content = [self.net_choices, self.net_text1,
self.net_text2, self.net_text3]
self.fields = ["blank", "ifname", "onboot", "bootproto", "ipaddr",
"netmask", "gateway"]
self.defaults = \
{
"ifname": {"label": "Interface name:",
"tooltip": "Interface system identifier",
"value": "locked"},
"onboot": {"label": "Enable interface:",
"tooltip": "",
"value": "radio"},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"value": "radio",
"choices": ["Static", "DHCP"]},
"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": ""},
}
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 %s" % (
managediface_ip, socket.gethostname(),
socket.gethostname().split(".")[0]))
def check(self, args):
"""Validate that all fields have valid values and sanity checks."""
#Get field information
responses = dict()
self.parent.footer.set_text("Checking data...")
for index, fieldname in enumerate(self.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"] = "none"
else:
responses["bootproto"] = "dhcp"
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 = []
if responses["onboot"] == "no":
numactiveifaces = 0
for iface in self.netsettings:
if self.netsettings[iface]['addr'] != "":
numactiveifaces += 1
if numactiveifaces < 2 and \
self.netsettings[self.activeiface]['addr'] != "":
#Block user because puppet l23network fails if all intefaces
#are disabled.
errors.append("Cannot disable all interfaces.")
elif responses["bootproto"] == "dhcp":
self.parent.footer.set_text("Scanning for DHCP servers. "
"Please wait...")
self.parent.refreshScreen()
try:
dhcptimeout = 5
with timeout.run_with_timeout(dhcp_checker.utils.IfaceState,
[self.activeiface],
timeout=dhcptimeout) as iface:
dhcp_server_data = timeout.run_with_timeout(
dhcp_checker.api.check_dhcp_on_eth,
[iface, dhcptimeout], timeout=dhcptimeout)
except (KeyboardInterrupt, timeout.TimeoutError):
self.log.debug("DHCP scan timed out")
self.log.warning(traceback.format_exc())
dhcp_server_data = []
except Exception:
self.log.warning("dhcp_checker failed to check on %s"
% self.activeiface)
dhcp_server_data = []
responses["dhcp_nowait"] = False
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(
widget.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)
self.parent.refreshScreen()
responses["dhcp_nowait"] = True
#Check ipaddr, netmask, gateway only if static
elif responses["bootproto"] == "none":
try:
if netaddr.valid_ipv4(responses["ipaddr"]):
if not netaddr.IPAddress(responses["ipaddr"]):
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except (BadIPException, Exception):
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 BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except (BadIPException, Exception):
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 BadIPException("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 BadIPException("Gateway IP is not in same "
"subnet as IP address")
except (BadIPException, Exception) as e:
errors.append(e)
self.parent.footer.set_text("Scanning for duplicate IP address..")
if len(responses["ipaddr"]) > 0:
if self.netsettings[self.activeiface]['link'].upper() != "UP":
try:
network.upIface(self.activeiface)
except NetworkException as e:
errors.append("Cannot activate {0} to check for "
"duplicate IP.".format(self.activeiface))
# Bind arping to requested IP if it's already assigned
assigned_ips = [v.get('addr') for v in
self.netsettings.itervalues()]
arping_bind = responses["ipaddr"] in assigned_ips
if network.duplicateIPExists(responses["ipaddr"],
self.activeiface, arping_bind):
errors.append("Duplicate host found with IP {0}.".format(
responses["ipaddr"]))
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
self.log.error("Errors: %s %s" % (len(errors), errors))
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... (May take up to 20s)")
puppetclasses = []
#If there is a gateway configured in /etc/sysconfig/network, unset it
expr = '^GATEWAY=.*'
replace.replaceInFile("/etc/sysconfig/network", expr, "")
l3ifconfig = {'type': "resource",
'class': "l23network::l3::ifconfig",
'name': self.activeiface}
if responses["onboot"].lower() == "no":
params = {"ipaddr": "none",
"gateway": ""}
elif responses["bootproto"] == "dhcp":
self.unset_gateway()
if "dhcp_nowait" in responses.keys():
params = {"ipaddr": "dhcp",
"dhcp_nowait": responses["dhcp_nowait"]}
else:
params = {"ipaddr": "dhcp"}
else:
cidr = network.netmaskToCidr(responses["netmask"])
params = {"ipaddr": "{0}/{1}".format(responses["ipaddr"], cidr),
"check_by_ping": "none"}
if len(responses["gateway"]) > 1:
params["gateway"] = responses["gateway"]
self.unset_gateway()
l3ifconfig['params'] = params
puppetclasses.append(l3ifconfig)
self.log.info("Puppet data: %s" % (puppetclasses))
try:
self.parent.refreshScreen()
puppet.puppetApply(puppetclasses)
ModuleHelper.getNetwork(self)
gateway = self.get_default_gateway_linux()
if gateway is None:
gateway = ""
self.fixEtcHosts()
except Exception as e:
self.log.error(e)
self.parent.footer.set_text("Error applying changes. Check logs "
"for details.")
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return False
self.parent.footer.set_text("Changes successfully applied.")
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return True
def getNetwork(self):
ModuleHelper.getNetwork(self)
def getDHCP(self, iface):
return ModuleHelper.getDHCP(iface)
def get_default_gateway_linux(self):
return ModuleHelper.get_default_gateway_linux()
def unset_gateway(self):
"""Unset current gateway."""
command = "ip route del default dev $(ip ro | grep default"\
" | awk '{print $NF}')"
if self.get_default_gateway_linux() is None:
return True
try:
noout = open('/dev/null', 'w')
subprocess.call(command, stdout=noout, stderr=noout,
shell=True)
except OSError:
self.log.warning(traceback.format_exc())
self.log.error("Unable to unset gateway")
self.log.error("Command was: {0}".format(command))
self.parent.footer.set_text("Unable to unset gateway.")
return False
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
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
def radioSelect(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.extdhcp = (rb.base_widget.get_label() == "Yes")
break
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(self.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):
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
def cancel(self, button):
ModuleHelper.cancel(self, button)
self.setNetworkDetails()
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults, showallbuttons=True)