b86f1cdad8
1.As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2.In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Change-Id: I2b2c1e76c4dda9a106ff3783fe882fb8a0f78112
438 lines
19 KiB
Python
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)
|