fuel-menu/fuelmenu/modules/interfaces.py

438 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 logging
import netaddr
import re
import socket
import urwid
from fuelmenu.common import errors as f_errors
from fuelmenu.common import modulehelper
from fuelmenu.common import network
from fuelmenu.common import puppet
from fuelmenu.common import replace
import fuelmenu.common.urwidwrapper as widget
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.visible = True
self.netsettings = dict()
self.parent = parent
self.screen = None
self.log = logging
self.log.basicConfig(filename='./fuelmenu.log', level=logging.DEBUG)
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": "",
"type": modulehelper.WidgetType.RADIO,
"callback": self.radioSelect},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"type": modulehelper.WidgetType.RADIO,
"choices": ["Static", "DHCP"],
"callback": self.radioSelect},
"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 = []
# Check for the duplicate IP provided
for k, v in self.netsettings.items():
if (k != self.activeiface and responses["ipaddr"] != ''
and responses["ipaddr"] == v.get('addr')):
errors.append("The same IP address {0} is assigned for "
"interfaces '{1}' and '{2}'.".format(
responses["ipaddr"], k, self.activeiface))
break
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 interfaces
# 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
dhcp_server_data = network.search_external_dhcp(
self.activeiface, dhcptimeout)
except network.NetworkException:
self.log.warning("dhcp_checker failed to check on %s"
% self.activeiface)
dhcp_server_data = []
if len(dhcp_server_data) < 1:
errors.append("No DHCP servers found. Cannot enable DHCP")
# 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 f_errors.BadIPException("Not a valid IP address")
else:
raise f_errors.BadIPException("Not a valid IP address")
except (f_errors.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 f_errors.BadIPException("Not a valid IP address")
else:
raise f_errors.BadIPException("Not a valid IP address")
except (f_errors.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 f_errors.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 f_errors.BadIPException(
"Gateway IP is not in same "
"subnet as IP address")
except (f_errors.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 f_errors.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.log.error("Errors: %s %s" % (len(errors), errors))
modulehelper.ModuleHelper.display_failed_check_dialog(self, errors)
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def clear_gateways_except(self, iface):
def include_interface(name):
return name != iface and \
self.netsettings[name].get('addr') and \
self.netsettings[name].get('netmask')
return [{'type': "resource",
'class': "l23network::l3::ifconfig",
'name': name,
'params': {'ipaddr': network.addr_in_cidr_notation(
self.netsettings[name]['addr'],
self.netsettings[name]['netmask'])}}
for name in self.netsettings if include_interface(name)]
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)")
# Build puppet resources to apply l23network puppet module to enable
# network changes
puppetclasses = []
# FIXME(mattymo): install_bondtool param does not work (LP#1541028)
# The following 4 lines should be removed when fixed.
disable_bond = {
'type': "literal",
'name': 'K_mod <| title == "bonding" |> {ensure => absent} '}
puppetclasses.append(disable_bond)
# If there is a gateway configured in /etc/sysconfig/network, unset it
expr = '^GATEWAY=.*'
replace.replaceInFile("/etc/sysconfig/network", expr, "")
# Initialize l23network class for NetworkManager fixes
l23network = {'type': "resource",
'class': "class",
'name': "l23network",
'params': {'install_bondtool': False}}
puppetclasses.append(l23network)
# Prepare l23network interface configuration
l3ifconfig = {'type': "resource",
'class': "l23network::l3::ifconfig",
'name': self.activeiface}
additionalclasses = []
if responses["onboot"].lower() == "no":
params = {"ipaddr": "none",
"gateway": ""}
elif responses["bootproto"] == "dhcp":
additionalclasses = self.clear_gateways_except(self.activeiface)
params = {"ipaddr": "dhcp"}
else:
cidr = network.addr_in_cidr_notation(responses["ipaddr"],
responses["netmask"])
params = {"ipaddr": cidr,
"check_by_ping": "none"}
if len(responses["gateway"]) > 1:
params["gateway"] = responses["gateway"]
additionalclasses = self.clear_gateways_except(
self.activeiface)
puppetclasses.extend(additionalclasses)
l3ifconfig['params'] = params
puppetclasses.append(l3ifconfig)
self.log.info("Puppet data: %s" % (puppetclasses))
try:
self.parent.refreshScreen()
result = puppet.puppetApply(puppetclasses)
if not result:
raise Exception("Puppet apply failed")
modulehelper.ModuleHelper.getNetwork(self)
gateway = self.get_default_gateway_linux()
if gateway is None:
gateway = ""
self.fixEtcHosts()
if responses['bootproto'] == 'dhcp':
self.parent.dns_might_have_changed = True
except Exception as e:
self.log.error(e)
self.parent.footer.set_text("Error applying changes. Check logs "
"for details.")
modulehelper.ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return False
self.parent.footer.set_text("Changes successfully applied.")
modulehelper.ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return True
def getNetwork(self):
modulehelper.ModuleHelper.getNetwork(self)
def getDHCP(self, iface):
return modulehelper.ModuleHelper.getDHCP(iface)
def get_default_gateway_linux(self):
return modulehelper.ModuleHelper.get_default_gateway_linux()
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.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[1].set_state(True)
rb_group[0].set_state(False)
else:
rb_group[1].set_state(False)
rb_group[0].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.ModuleHelper.getNetwork(self)
self.setNetworkDetails()
def cancel(self, button):
modulehelper.ModuleHelper.cancel(self, button)
self.setNetworkDetails()
def screenUI(self):
return modulehelper.ModuleHelper.screenUI(self, self.header_content,
self.fields,
self.defaults,
show_all_buttons=True)