fuel-menu/fuelmenu/modules/interfaces.py

437 lines
18 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 six
import socket
import urwid
from fuelmenu.common.errors import BadIPException
from fuelmenu.common.errors import NetworkException
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common.modulehelper import WidgetType
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": WidgetType.RADIO,
"callback": self.radioSelect},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"type": 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 six.iteritems(self.netsettings):
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 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.log.error("Errors: %s %s" % (len(errors), errors))
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 result is False:
raise Exception("Puppet apply failed")
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.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 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[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.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, show_all_buttons=True)