Deprecate fuelmenu directory
Change-Id: Ia6affb462ada7b09e531e9cd096a2e109ac51a9a Related-Bug: #1506885
This commit is contained in:
parent
92596c1927
commit
3119b6f5bc
|
@ -26,11 +26,6 @@ description:
|
|||
|
||||
maintainers:
|
||||
|
||||
- fuelmenu/:
|
||||
- name: Matthew Mosesohn
|
||||
email: mmosesohn@mirantis.com
|
||||
IRC: mattymo
|
||||
|
||||
- fuel_upgrade_system/fuel_package_updates/:
|
||||
- name: Matthew Mosesohn
|
||||
email: mmosesohn@mirantis.com
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
recursive-include fuelmenu *.py *.yaml *.default
|
||||
include setup.py
|
||||
include README
|
|
@ -1,25 +0,0 @@
|
|||
Fuel menu
|
||||
|
||||
This tool is used to perform setup of network interfaces, as well as configure
|
||||
Cobbler parameters. The framework is extensible.
|
||||
|
||||
Plugin guidelines:
|
||||
|
||||
Create a python class with a filename matching the class:
|
||||
class foo(urwid.Widget) and foo.py
|
||||
|
||||
Place this file in the Fuel Menu modules folder.
|
||||
|
||||
Plugin class should define the following functions:
|
||||
__init__(self, parent)
|
||||
check(self, args)
|
||||
apply(self, args)
|
||||
save(self) #Still need to complete
|
||||
load(self) #Still need to complete
|
||||
screenUI(self)
|
||||
|
||||
screenUI should use urwidwrapper class to define and set up all UI elements
|
||||
Note that you need to specify a function for buttons and radio button groups
|
||||
for them to work properly. Check and Apply buttons should point to check and
|
||||
apply functions, respectively.
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# 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.
|
|
@ -1,13 +0,0 @@
|
|||
# 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.
|
|
@ -1,61 +0,0 @@
|
|||
#!/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 fuelmenu.common.urwidwrapper as widget
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class ModalDialog(urwid.WidgetWrap):
|
||||
signals = ['close']
|
||||
|
||||
title = None
|
||||
|
||||
def __init__(self, title, body, escape_key, previous_widget, loop=None):
|
||||
self.escape_key = escape_key
|
||||
self.previous_widget = previous_widget
|
||||
self.keep_open = True
|
||||
self.loop = loop
|
||||
|
||||
if type(body) in [str, unicode]:
|
||||
body = urwid.Text(body)
|
||||
self.title = title
|
||||
bodybox = urwid.LineBox(urwid.Pile([body, blank,
|
||||
widget.Button("Close", self.close)]), title)
|
||||
overlay = urwid.Overlay(urwid.Filler(bodybox), previous_widget,
|
||||
'center', ('relative', 80), 'middle',
|
||||
('relative', 80))
|
||||
overlay_attrmap = urwid.AttrMap(overlay, "body")
|
||||
super(ModalDialog, self).__init__(overlay_attrmap)
|
||||
|
||||
def close(self, arg):
|
||||
urwid.emit_signal(self, "close")
|
||||
self.keep_open = False
|
||||
self.loop.widget = self.previous_widget
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s title='%s' at %s>" % (self.__class__.__name__, self.title,
|
||||
hex(id(self)))
|
||||
|
||||
|
||||
def display_dialog(self, body, title, escape_key="esc"):
|
||||
filler = urwid.Pile([body])
|
||||
dialog = ModalDialog(title, filler, escape_key,
|
||||
self.parent.mainloop.widget,
|
||||
loop=self.parent.mainloop)
|
||||
self.parent.mainloop.widget = dialog
|
||||
return dialog
|
|
@ -1,22 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
|
||||
class NetworkException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BadIPException(Exception):
|
||||
pass
|
|
@ -1,216 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 fuelmenu.common.urwidwrapper as widget
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import netifaces
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.modulehelper')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class ModuleHelper(object):
|
||||
|
||||
@classmethod
|
||||
def load(cls, modobj):
|
||||
#Read in yaml
|
||||
defaultsettings = Settings().read(modobj.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings.copy()
|
||||
oldsettings.update(Settings().read(modobj.parent.settingsfile))
|
||||
for setting in modobj.defaults.keys():
|
||||
if "label" in setting:
|
||||
continue
|
||||
elif "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
modobj.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
modobj.defaults[setting]["value"] = oldsettings[setting]
|
||||
if modobj.netsettings and oldsettings["ADMIN_NETWORK"]["interface"] \
|
||||
in modobj.netsettings.keys():
|
||||
modobj.activeiface = oldsettings["ADMIN_NETWORK"]["interface"]
|
||||
return oldsettings
|
||||
|
||||
@classmethod
|
||||
def save(cls, modobj, responses):
|
||||
newsettings = dict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
newsettings[part1] = modobj.oldsettings[part1]
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
return newsettings
|
||||
|
||||
@classmethod
|
||||
def cancel(self, cls, button=None):
|
||||
for index, fieldname in enumerate(cls.fields):
|
||||
if fieldname != "blank" and "label" not in fieldname:
|
||||
try:
|
||||
cls.edits[index].set_edit_text(cls.defaults[fieldname][
|
||||
'value'])
|
||||
except AttributeError:
|
||||
log.warning("Field %s unable to reset text" % fieldname)
|
||||
|
||||
@classmethod
|
||||
def screenUI(cls, modobj, headertext, fields, defaults,
|
||||
showallbuttons=False, buttons_visible=True):
|
||||
|
||||
log.debug("Preparing screen UI for %s" % modobj.name)
|
||||
#Define text labels, text fields, and buttons first
|
||||
header_content = []
|
||||
for text in headertext:
|
||||
if isinstance(text, str):
|
||||
header_content.append(urwid.Text(text))
|
||||
else:
|
||||
header_content.append(text)
|
||||
|
||||
edits = []
|
||||
toolbar = modobj.parent.footer
|
||||
for key in fields:
|
||||
#Example: key = hostname, label = Hostname, value = fuel-pm
|
||||
if key == "blank":
|
||||
edits.append(blank)
|
||||
elif defaults[key]["value"] == "radio":
|
||||
label = widget.TextLabel(defaults[key]["label"])
|
||||
if "choices" in defaults[key]:
|
||||
choices_list = defaults[key]["choices"]
|
||||
else:
|
||||
choices_list = ["Yes", "No"]
|
||||
choices = widget.ChoicesGroup(choices_list,
|
||||
default_value="Yes",
|
||||
fn=modobj.radioSelect)
|
||||
columns = widget.Columns([('weight', 2, label),
|
||||
('weight', 3, choices)])
|
||||
#Attach choices rb_group so we can use it later
|
||||
columns.rb_group = choices.rb_group
|
||||
edits.append(columns)
|
||||
elif defaults[key]["value"] == "label":
|
||||
edits.append(widget.TextLabel(defaults[key]["label"]))
|
||||
else:
|
||||
ispassword = "PASSWORD" in key.upper()
|
||||
caption = defaults[key]["label"]
|
||||
default = defaults[key]["value"]
|
||||
tooltip = defaults[key]["tooltip"]
|
||||
edits.append(
|
||||
widget.TextField(key, caption, 23, default, tooltip,
|
||||
toolbar, ispassword=ispassword))
|
||||
|
||||
listbox_content = []
|
||||
listbox_content.extend(header_content)
|
||||
listbox_content.append(blank)
|
||||
listbox_content.extend(edits)
|
||||
listbox_content.append(blank)
|
||||
|
||||
#Wrap buttons into Columns so it doesn't expand and look ugly
|
||||
if buttons_visible:
|
||||
#Button to check
|
||||
button_check = widget.Button("Check", modobj.check)
|
||||
#Button to revert to previously saved settings
|
||||
button_cancel = widget.Button("Cancel", modobj.cancel)
|
||||
#Button to apply (and check again)
|
||||
button_apply = widget.Button("Apply", modobj.apply)
|
||||
|
||||
if modobj.parent.globalsave and showallbuttons is False:
|
||||
check_col = widget.Columns([button_check])
|
||||
else:
|
||||
check_col = widget.Columns([button_check, button_cancel,
|
||||
button_apply, ('weight', 2, blank)])
|
||||
listbox_content.append(check_col)
|
||||
|
||||
#Add everything into a ListBox and return it
|
||||
listwalker = widget.TabbedListWalker(listbox_content)
|
||||
screen = urwid.ListBox(listwalker)
|
||||
modobj.edits = edits
|
||||
modobj.walker = listwalker
|
||||
modobj.listbox_content = listbox_content
|
||||
return screen
|
||||
|
||||
@classmethod
|
||||
def getNetwork(cls, modobj):
|
||||
"""Returns addr, broadcast, netmask for each network interface."""
|
||||
re_ifaces = re.compile(r"lo|vir|vbox|docker|veth")
|
||||
for iface in netifaces.interfaces():
|
||||
if re_ifaces.search(iface):
|
||||
continue
|
||||
try:
|
||||
modobj.netsettings.update({iface: netifaces.ifaddresses(iface)[
|
||||
netifaces.AF_INET][0]})
|
||||
modobj.netsettings[iface]["onboot"] = "Yes"
|
||||
except (TypeError, KeyError):
|
||||
modobj.netsettings.update({iface: {"addr": "", "netmask": "",
|
||||
"onboot": "no"}})
|
||||
modobj.netsettings[iface]['mac'] = netifaces.ifaddresses(iface)[
|
||||
netifaces.AF_LINK][0]['addr']
|
||||
|
||||
#Set link state
|
||||
try:
|
||||
with open("/sys/class/net/%s/operstate" % iface) as f:
|
||||
content = f.readlines()
|
||||
modobj.netsettings[iface]["link"] = content[0].strip()
|
||||
except IOError:
|
||||
log.warning("Unable to read operstate file for %s" % iface)
|
||||
modobj.netsettings[iface]["link"] = "unknown"
|
||||
#Change unknown link state to up if interface has an IP
|
||||
if modobj.netsettings[iface]["link"] == "unknown":
|
||||
if modobj.netsettings[iface]["addr"] != "":
|
||||
modobj.netsettings[iface]["link"] = "up"
|
||||
|
||||
#Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV
|
||||
modobj.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):
|
||||
modobj.netsettings[iface]['bootproto'] = \
|
||||
line.split('=')[1].strip()
|
||||
break
|
||||
except Exception:
|
||||
#Check for dhclient process running for this interface
|
||||
if modobj.getDHCP(iface):
|
||||
modobj.netsettings[iface]['bootproto'] = "dhcp"
|
||||
else:
|
||||
modobj.netsettings[iface]['bootproto'] = "none"
|
||||
modobj.gateway = modobj.get_default_gateway_linux()
|
||||
|
||||
@classmethod
|
||||
def getDHCP(cls, iface):
|
||||
"""Returns True if the interface has a dhclient process running."""
|
||||
noout = open('/dev/null', 'w')
|
||||
dhclient_running = subprocess.call(["pgrep", "-f", "dhclient.*%s" %
|
||||
(iface)], stdout=noout,
|
||||
stderr=noout)
|
||||
return (dhclient_running == 0)
|
||||
|
||||
@classmethod
|
||||
def get_default_gateway_linux(cls):
|
||||
"""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)))
|
|
@ -1,107 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from fuelmenu.common.errors import BadIPException
|
||||
from fuelmenu.common.errors import NetworkException
|
||||
|
||||
import netaddr
|
||||
import subprocess
|
||||
|
||||
|
||||
def inSameSubnet(ip1, ip2, netmask_or_cidr):
|
||||
try:
|
||||
cidr1 = netaddr.IPNetwork("%s/%s" % (ip1, netmask_or_cidr))
|
||||
cidr2 = netaddr.IPNetwork("%s/%s" % (ip2, netmask_or_cidr))
|
||||
return cidr1 == cidr2
|
||||
except netaddr.AddrFormatError:
|
||||
return False
|
||||
|
||||
|
||||
def getCidr(ip, netmask):
|
||||
try:
|
||||
ipn = netaddr.IPNetwork("%s/%s" % (ip, netmask))
|
||||
return str(ipn.cidr)
|
||||
except netaddr.AddrFormatError:
|
||||
return False
|
||||
|
||||
|
||||
def getCidrSize(cidr):
|
||||
try:
|
||||
ipn = netaddr.IPNetwork(cidr)
|
||||
return ipn.size
|
||||
except netaddr.AddrFormatError:
|
||||
return False
|
||||
|
||||
|
||||
def getNetwork(ip, netmask, additionalip=None):
|
||||
#Return a list excluding ip and broadcast IPs
|
||||
try:
|
||||
ipn = netaddr.IPNetwork("%s/%s" % (ip, netmask))
|
||||
ipn_list = list(ipn)
|
||||
#Drop broadcast and network ip
|
||||
ipn_list = ipn_list[1:-1]
|
||||
#Drop ip
|
||||
ipn_list[:] = [value for value in ipn_list if str(value) != ip]
|
||||
#Drop additionalip
|
||||
if additionalip:
|
||||
ipn_list[:] = [value for value in ipn_list if
|
||||
str(value) != additionalip]
|
||||
|
||||
return ipn_list
|
||||
except netaddr.AddrFormatError:
|
||||
return False
|
||||
|
||||
|
||||
def range(startip, endip):
|
||||
#Return a list of IPs between startip and endip
|
||||
try:
|
||||
return set(netaddr.iter_iprange(startip, endip))
|
||||
except netaddr.AddrFormatError:
|
||||
raise BadIPException("Invalid IP address(es) specified.")
|
||||
|
||||
|
||||
def intersects(range1, range2):
|
||||
#Returns true if any IPs in range1 exist in range2
|
||||
return range1 & range2
|
||||
|
||||
|
||||
def netmaskToCidr(netmask):
|
||||
return sum([bin(int(x)).count('1') for x in netmask.split('.')])
|
||||
|
||||
|
||||
def duplicateIPExists(ip, iface, arping_bind=False):
|
||||
"""Checks for duplicate IP addresses using arping
|
||||
Don't use arping_bind unless you know what you are doing.
|
||||
|
||||
:param ip: IP to scan for
|
||||
:param iface: Interface on which to send requests
|
||||
:param arping_bind: Bind to IP when probing (IP must be already assigned.)
|
||||
:returns: boolean
|
||||
"""
|
||||
noout = open('/dev/null', 'w')
|
||||
if arping_bind:
|
||||
bind_ip = ip
|
||||
else:
|
||||
bind_ip = "0.0.0.0"
|
||||
no_dupes = subprocess.call(["arping", "-D", "-c3", "-w1", "-I", iface,
|
||||
"-s", bind_ip, ip], stdout=noout, stderr=noout)
|
||||
return (no_dupes != 0)
|
||||
|
||||
|
||||
def upIface(iface):
|
||||
noout = open('/dev/null', 'w')
|
||||
result = subprocess.call(["ifconfig", iface, "up"], stdout=noout,
|
||||
stderr=noout)
|
||||
if result != 0:
|
||||
raise NetworkException("Failed to up interface {0}".format(iface))
|
|
@ -1,80 +0,0 @@
|
|||
# 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 subprocess
|
||||
|
||||
#Python 2.6 hack to add check_output command
|
||||
|
||||
if "check_output" not in dir(subprocess): # duck punch it in!
|
||||
def f(*popenargs, **kwargs):
|
||||
if 'stdout' in kwargs:
|
||||
raise ValueError('stdout argument not allowed, \
|
||||
itwill be overridden.')
|
||||
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs,
|
||||
**kwargs)
|
||||
output, unused_err = process.communicate()
|
||||
retcode = process.poll()
|
||||
if retcode:
|
||||
cmd = kwargs.get("args")
|
||||
if cmd is None:
|
||||
cmd = popenargs[0]
|
||||
raise Exception(retcode, cmd)
|
||||
return output
|
||||
subprocess.check_output = f
|
||||
|
||||
|
||||
def puppetApply(classes):
|
||||
#name should be a string
|
||||
#params should be a dict or list of dicts
|
||||
'''Runs puppet apply -e "classname {'name': params}".'''
|
||||
log = logging
|
||||
log.info("Puppet start")
|
||||
|
||||
command = ["puppet", "apply", "-d", "-v", "--logdest",
|
||||
"/var/log/puppet/fuelmenu-puppet.log"]
|
||||
input = []
|
||||
for cls in classes:
|
||||
if cls['type'] == "resource":
|
||||
input.extend([cls["class"], "{", '"%s":' % cls["name"]])
|
||||
elif cls['type'] == "class":
|
||||
input.extend(["class", "{", '"%s":' % cls["class"]])
|
||||
else:
|
||||
log.error("Invalid type %s" % cls['type'])
|
||||
return False
|
||||
#Build params
|
||||
for key, value in cls["params"].iteritems():
|
||||
if type(value) == bool:
|
||||
input.extend([key, "=>", '%s,' % str(value).lower()])
|
||||
else:
|
||||
input.extend([key, "=>", '"%s",' % value])
|
||||
input.append('}')
|
||||
|
||||
log.debug(' '.join(command))
|
||||
log.debug(' '.join(input))
|
||||
output = ""
|
||||
try:
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
output, errout = process.communicate(input=' '.join(input))
|
||||
except Exception as e:
|
||||
import traceback
|
||||
log.error(traceback.print_exc())
|
||||
if "err:" in output:
|
||||
log.error(e)
|
||||
return False
|
||||
else:
|
||||
log.debug(output)
|
||||
return True
|
|
@ -1,25 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from random import choice
|
||||
import string
|
||||
|
||||
|
||||
def password(arg=None):
|
||||
try:
|
||||
length = int(arg)
|
||||
except Exception:
|
||||
length = 8
|
||||
chars = string.letters + string.digits
|
||||
return ''.join([choice(chars) for _ in xrange(length)])
|
|
@ -1,23 +0,0 @@
|
|||
# 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 re
|
||||
|
||||
|
||||
def replaceInFile(filename, orig, new):
|
||||
lines = open(filename).readlines()
|
||||
for lineno, line in enumerate(lines):
|
||||
lines[lineno] = re.sub(orig, new, line)
|
||||
with open(filename, 'w') as f:
|
||||
f.write("".join(lines))
|
|
@ -1,40 +0,0 @@
|
|||
# Copyright 2014 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 thread
|
||||
from threading import Timer
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def handler(signum, frame):
|
||||
raise TimeoutError('Timeout error')
|
||||
|
||||
|
||||
def run_with_timeout(check, args=None, kwargs=None, timeout=60, default=False):
|
||||
args = args if args else []
|
||||
kwargs = kwargs if kwargs else dict()
|
||||
if not timeout:
|
||||
return check(*args, **kwargs)
|
||||
try:
|
||||
timeout_timer = Timer(timeout, thread.interrupt_main)
|
||||
timeout_timer.start()
|
||||
result = check(*args, **kwargs)
|
||||
return result
|
||||
except KeyboardInterrupt:
|
||||
return default
|
||||
finally:
|
||||
timeout_timer.cancel()
|
|
@ -1,270 +0,0 @@
|
|||
# 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 urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.urwidwrapper')
|
||||
|
||||
|
||||
def TextField(keyword, label, width, default_value=None, tooltip=None,
|
||||
toolbar=None, disabled=False, ispassword=False):
|
||||
"""Returns an Urwid Edit object."""
|
||||
if ispassword:
|
||||
mask = "*"
|
||||
else:
|
||||
mask = None
|
||||
if not tooltip:
|
||||
edit_obj = urwid.Edit(('important', label.ljust(width)), default_value,
|
||||
mask=mask)
|
||||
else:
|
||||
edit_obj = TextWithTip(('important', label.ljust(width)),
|
||||
default_value, tooltip, toolbar, mask=mask)
|
||||
wrapped_obj = urwid.AttrWrap(edit_obj, 'editbx', 'editfc')
|
||||
if disabled:
|
||||
wrapped_obj = urwid.WidgetDisable(urwid.AttrWrap(edit_obj,
|
||||
'important', 'editfc'))
|
||||
#Add get_edit_text and set_edit_text to wrapped_obj so we can use later
|
||||
wrapped_obj.set_edit_text = edit_obj.set_edit_text
|
||||
wrapped_obj.get_edit_text = edit_obj.get_edit_text
|
||||
return wrapped_obj
|
||||
|
||||
|
||||
def ChoicesGroup(choices, default_value=None, fn=None):
|
||||
"""Returns list of RadioButtons in a one-line GridFlow."""
|
||||
rb_group = []
|
||||
|
||||
for txt in choices:
|
||||
is_default = True if txt == default_value else False
|
||||
urwid.AttrWrap(urwid.RadioButton(rb_group, txt,
|
||||
is_default, on_state_change=fn,
|
||||
user_data=txt),
|
||||
'buttn', 'buttnf')
|
||||
wrapped_choices = TabbedGridFlow(rb_group, 13, 3, 0, 'left')
|
||||
#Bundle rb_group so it can be used later easily
|
||||
wrapped_choices.rb_group = rb_group
|
||||
return wrapped_choices
|
||||
|
||||
|
||||
def TextLabel(text):
|
||||
"""Returns an Urwid text object."""
|
||||
return urwid.Text(text)
|
||||
|
||||
|
||||
def Columns(objects):
|
||||
"""Returns a padded Urwid Columns object that is left aligned."""
|
||||
# Objects is a list of widgets. Widgets may be optionally specified
|
||||
# as a tuple with ('weight', weight, widget) or (width, widget).
|
||||
# Tuples without a widget have a weight of 1."""
|
||||
return urwid.Padding(TabbedColumns(objects, 1),
|
||||
left=0, right=0, min_width=61)
|
||||
|
||||
|
||||
def Button(text, fn):
|
||||
"""Returns a wrapped Button with reverse focus attribute."""
|
||||
button = urwid.Button(text, fn)
|
||||
return urwid.AttrMap(button, None, focus_map='reversed')
|
||||
|
||||
|
||||
class TabbedGridFlow(urwid.GridFlow):
|
||||
|
||||
def __init__(self, cells, cell_width, h_sep, v_sep, align):
|
||||
urwid.GridFlow.__init__(self, cells=cells, cell_width=cell_width,
|
||||
h_sep=h_sep, v_sep=v_sep, align=align)
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == 'tab' and self.focus_position < (len(self.contents) - 1)\
|
||||
and self.contents[self.focus_position + 1][0].selectable():
|
||||
self.tab_next(self.focus_position)
|
||||
elif key == 'shift tab' and self.focus_position > 0 \
|
||||
and self.contents[self.focus_position - 1][0].selectable():
|
||||
self.tab_prev(self.focus_position)
|
||||
else:
|
||||
return self.__super.keypress(size, key)
|
||||
|
||||
def tab_next(self, pos):
|
||||
self.set_focus(pos + 1)
|
||||
maxlen = (len(self.contents) - 1)
|
||||
while pos < maxlen:
|
||||
if self.contents[pos][0].selectable():
|
||||
return
|
||||
else:
|
||||
pos += 1
|
||||
|
||||
if pos >= maxlen:
|
||||
pos = 0
|
||||
self.set_focus(pos)
|
||||
|
||||
def tab_prev(self, pos):
|
||||
self.set_focus(pos - 1)
|
||||
while pos > 0:
|
||||
if self.contents[pos][0].selectable():
|
||||
return
|
||||
else:
|
||||
pos -= 1
|
||||
if pos == 0:
|
||||
pos = (len(self.contents) - 1)
|
||||
|
||||
self.set_focus(pos)
|
||||
|
||||
def first_selectable(self):
|
||||
'''returns index of first selectable widget in contents.'''
|
||||
for pos, item in enumerate(self.contents):
|
||||
if item[0].selectable():
|
||||
return pos
|
||||
return (len(self.contents) - 1)
|
||||
|
||||
|
||||
class TabbedColumns(urwid.Columns):
|
||||
|
||||
def __init__(self, widget_list, dividechars=0, focus_column=None,
|
||||
min_width=1, box_columns=None):
|
||||
urwid.Columns.__init__(self, widget_list,
|
||||
dividechars=dividechars,
|
||||
focus_column=focus_column,
|
||||
min_width=min_width,
|
||||
box_columns=box_columns)
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == 'tab' and self.focus_position < (len(self.contents) - 1)\
|
||||
and self.widget_list[self.focus_position + 1].selectable():
|
||||
self.tab_next(self.focus_position)
|
||||
elif key == 'shift tab' and self.focus_position > 0 \
|
||||
and self.widget_list[self.focus_position - 1].selectable():
|
||||
self.tab_prev(self.focus_position)
|
||||
else:
|
||||
return self.__super.keypress(size, key)
|
||||
|
||||
def tab_next(self, pos):
|
||||
self.set_focus(pos + 1)
|
||||
maxlen = (len(self.contents) - 1)
|
||||
while pos < maxlen:
|
||||
if self.widget_list[pos].selectable():
|
||||
return
|
||||
else:
|
||||
pos += 1
|
||||
|
||||
if pos >= maxlen:
|
||||
pos = 0
|
||||
self.set_focus(pos)
|
||||
|
||||
def tab_prev(self, pos):
|
||||
self.set_focus(pos - 1)
|
||||
while pos > 0:
|
||||
if self.widget_list[pos].selectable():
|
||||
return
|
||||
else:
|
||||
pos -= 1
|
||||
if pos == 0:
|
||||
pos = (len(self.widget_list) - 1)
|
||||
|
||||
self.set_focus(pos)
|
||||
|
||||
def first_selectable(self):
|
||||
'''returns index of first selectable widget in widget_list.'''
|
||||
for pos, item in enumerate(self.widget_list):
|
||||
if item.selectable():
|
||||
return pos
|
||||
return (len(self.widget_list) - 1)
|
||||
|
||||
|
||||
class TextWithTip(urwid.Edit):
|
||||
def __init__(self, label, default_value=None, tooltip=None, toolbar=None,
|
||||
mask=None):
|
||||
urwid.Edit.__init__(self, caption=label, edit_text=default_value,
|
||||
mask=mask)
|
||||
self.tip = tooltip
|
||||
self.toolbar = toolbar
|
||||
|
||||
def render(self, size, focus=False):
|
||||
if focus:
|
||||
self.toolbar.set_text(self.tip)
|
||||
canv = super(TextWithTip, self).render(size, focus)
|
||||
return canv
|
||||
|
||||
|
||||
class TabbedListWalker(urwid.ListWalker):
|
||||
def __init__(self, lst):
|
||||
self.lst = lst
|
||||
self.focus = 0
|
||||
|
||||
def _modified(self):
|
||||
return urwid.ListWalker._modified(self)
|
||||
|
||||
def tab_next(self):
|
||||
item, pos = self.get_next(self.focus)
|
||||
while pos is not None:
|
||||
if item.selectable():
|
||||
break
|
||||
else:
|
||||
item, pos = self.get_next(pos)
|
||||
if pos is None:
|
||||
pos = 0
|
||||
self.focus = pos
|
||||
self._modified()
|
||||
try:
|
||||
#Reset focus to first selectable widget in item
|
||||
if hasattr(item, 'original_widget'):
|
||||
item.original_widget.set_focus(
|
||||
item.original_widget.first_selectable())
|
||||
else:
|
||||
item.set_focus(item.first_selectable())
|
||||
except Exception:
|
||||
#Ignore failure. Case only applies to TabbedColumns and
|
||||
#TabbedGridFlow. Other items should fail silently.
|
||||
pass
|
||||
|
||||
def tab_prev(self):
|
||||
item, pos = self.get_prev(self.focus)
|
||||
while pos is not None:
|
||||
if item.selectable():
|
||||
break
|
||||
else:
|
||||
item, pos = self.get_prev(pos)
|
||||
|
||||
if pos is None:
|
||||
pos = (len(self.lst) - 1)
|
||||
|
||||
self.focus = pos
|
||||
self._modified()
|
||||
try:
|
||||
if hasattr(item, 'original_widget'):
|
||||
item.original_widget.set_focus(
|
||||
len(item.original_widget.contents) - 1)
|
||||
else:
|
||||
item.set_focus(len(item.contents) - 1)
|
||||
except Exception:
|
||||
#Ignore failure. Case only applies to TabbedColumns and
|
||||
#TabbedGridFlow. Other items should fail silently.
|
||||
pass
|
||||
|
||||
def get_focus(self):
|
||||
if self.lst:
|
||||
return self.lst[self.focus], self.focus
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def set_focus(self, focus):
|
||||
self.focus = focus
|
||||
|
||||
def get_next(self, pos):
|
||||
if (pos + 1) >= len(self.lst):
|
||||
return None, None
|
||||
return self.lst[pos + 1], pos + 1
|
||||
|
||||
def get_prev(self, pos):
|
||||
if (pos - 1) < 0:
|
||||
return None, None
|
||||
return self.lst[pos - 1], pos - 1
|
|
@ -1,479 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
from common import dialog
|
||||
from common import timeout
|
||||
from common import urwidwrapper as widget
|
||||
import dhcp_checker.api
|
||||
import dhcp_checker.utils
|
||||
import logging
|
||||
import operator
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
from settings import Settings
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
|
||||
# set up logging
|
||||
logging.basicConfig(filename='/var/log/fuelmenu.log',
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
level=logging.DEBUG)
|
||||
log = logging.getLogger('fuelmenu.loader')
|
||||
|
||||
|
||||
class Loader(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.modlist = []
|
||||
self.choices = []
|
||||
self.child = None
|
||||
self.children = []
|
||||
self.childpage = None
|
||||
self.parent = parent
|
||||
|
||||
def load_modules(self, module_dir):
|
||||
if module_dir not in sys.path:
|
||||
sys.path.append(module_dir)
|
||||
|
||||
modules = [os.path.splitext(f)[0] for f in os.listdir(module_dir)
|
||||
if f.endswith('.py')]
|
||||
|
||||
for module in modules:
|
||||
log.info('loading module %s' % module)
|
||||
try:
|
||||
imported = __import__(module)
|
||||
pass
|
||||
except ImportError as e:
|
||||
log.error('module could not be imported: %s' % e)
|
||||
continue
|
||||
|
||||
clsobj = getattr(imported, module, None)
|
||||
modobj = clsobj(self.parent)
|
||||
|
||||
# add the module to the list
|
||||
self.modlist.append(modobj)
|
||||
# sort modules
|
||||
self.modlist.sort(key=operator.attrgetter('priority'))
|
||||
for module in self.modlist:
|
||||
self.choices.append(module.name)
|
||||
return (self.modlist, self.choices)
|
||||
|
||||
|
||||
class FuelSetup(object):
|
||||
|
||||
def __init__(self):
|
||||
self.footer = None
|
||||
self.frame = None
|
||||
self.screen = None
|
||||
self.defaultsettingsfile = os.path.join(os.path.dirname(__file__),
|
||||
"settings.yaml")
|
||||
self.settingsfile = "/etc/fuel/astute.yaml"
|
||||
self.managediface = "eth0"
|
||||
#Set to true to move all settings to end
|
||||
self.globalsave = True
|
||||
self.version = self.getVersion("/etc/fuel/version.yaml")
|
||||
self.main()
|
||||
self.choices = []
|
||||
|
||||
def menu(self, title, choices):
|
||||
body = [urwid.Text(title), urwid.Divider()]
|
||||
for c in choices:
|
||||
button = urwid.Button(c)
|
||||
urwid.connect_signal(button, 'click', self.menu_chosen, c)
|
||||
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
|
||||
return urwid.ListBox(urwid.SimpleListWalker(body))
|
||||
#return urwid.ListBox(urwid.SimpleFocusListWalker(body))
|
||||
|
||||
def menu_chosen(self, button, choice):
|
||||
size = self.screen.get_cols_rows()
|
||||
self.screen.draw_screen(size, self.frame.render(size))
|
||||
for item in self.menuitems.body.contents:
|
||||
try:
|
||||
if item.original_widget and \
|
||||
item.original_widget.get_label() == choice:
|
||||
item.set_attr_map({None: 'header'})
|
||||
else:
|
||||
item.set_attr_map({None: None})
|
||||
except Exception as e:
|
||||
log.info("%s" % item)
|
||||
log.error("%s" % e)
|
||||
self.setChildScreen(name=choice)
|
||||
|
||||
def setChildScreen(self, name=None):
|
||||
if name is None:
|
||||
self.child = self.children[0]
|
||||
else:
|
||||
self.child = self.children[int(self.choices.index(name))]
|
||||
if not self.child.screen:
|
||||
self.child.screen = self.child.screenUI()
|
||||
self.childpage = self.child.screen
|
||||
self.childfill = urwid.Filler(self.childpage, 'top', 40)
|
||||
self.childbox = urwid.BoxAdapter(self.childfill, 40)
|
||||
self.cols = urwid.Columns(
|
||||
[
|
||||
('fixed', 20, urwid.Pile([
|
||||
urwid.AttrMap(self.menubox, 'bright'),
|
||||
urwid.Divider(" ")])),
|
||||
('weight', 3, urwid.Pile([
|
||||
urwid.Divider(" "),
|
||||
self.childbox,
|
||||
urwid.Divider(" ")]))
|
||||
], 1)
|
||||
self.child.refresh()
|
||||
self.listwalker[:] = [self.cols]
|
||||
|
||||
def refreshScreen(self):
|
||||
size = self.screen.get_cols_rows()
|
||||
self.screen.draw_screen(size, self.frame.render(size))
|
||||
|
||||
def refreshChildScreen(self, name):
|
||||
child = self.children[int(self.choices.index(name))]
|
||||
#Refresh child listwalker
|
||||
child.listwalker[:] = child.listbox_content
|
||||
|
||||
#reassign childpage top level objects
|
||||
self.childpage = urwid.ListBox(child.listwalker)
|
||||
self.childfill = urwid.Filler(self.childpage, 'middle', 22)
|
||||
self.childbox = urwid.BoxAdapter(self.childfill, 22)
|
||||
self.cols = urwid.Columns(
|
||||
[
|
||||
('fixed', 20, urwid.Pile([
|
||||
urwid.AttrMap(self.menubox, 'bright'),
|
||||
urwid.Divider(" ")])),
|
||||
('weight', 3, urwid.Pile([
|
||||
urwid.Divider(" "),
|
||||
self.childbox,
|
||||
urwid.Divider(" ")]))
|
||||
], 1)
|
||||
#Refresh top level listwalker
|
||||
#self.listwalker[:] = [self.cols]
|
||||
|
||||
def getVersion(self, versionfile):
|
||||
try:
|
||||
versiondata = Settings().read(versionfile)
|
||||
return versiondata['VERSION']['release']
|
||||
except (IOError, KeyError):
|
||||
log.error("Unable to set Fuel version from %s" % versionfile)
|
||||
return ""
|
||||
|
||||
def main(self):
|
||||
#Disable kernel print messages. They make our UI ugly
|
||||
noout = open('/dev/null', 'w')
|
||||
subprocess.call(["sysctl", "-w", "kernel.printk=4 1 1 7"],
|
||||
stdout=noout, stderr=noout)
|
||||
|
||||
text_header = (u"Fuel %s setup "
|
||||
u"Use Up/Down/Left/Right to navigate. F8 exits."
|
||||
% self.version)
|
||||
text_footer = (u"Status messages go here.")
|
||||
|
||||
#Top and bottom lines of frame
|
||||
self.header = urwid.AttrWrap(urwid.Text(text_header), 'header')
|
||||
self.footer = urwid.AttrWrap(urwid.Text(text_footer), 'footer')
|
||||
|
||||
#Prepare submodules
|
||||
loader = Loader(self)
|
||||
moduledir = "%s/modules" % (os.path.dirname(__file__))
|
||||
self.children, self.choices = loader.load_modules(module_dir=moduledir)
|
||||
|
||||
if len(self.children) == 0:
|
||||
import sys
|
||||
sys.exit(1)
|
||||
#Build list of choices excluding visible
|
||||
self.visiblechoices = []
|
||||
for child, choice in zip(self.children, self.choices):
|
||||
if child.visible:
|
||||
self.visiblechoices.append(choice)
|
||||
|
||||
self.menuitems = self.menu(u'Menu', self.visiblechoices)
|
||||
menufill = urwid.Filler(self.menuitems, 'top', 40)
|
||||
self.menubox = urwid.BoxAdapter(menufill, 40)
|
||||
|
||||
self.child = self.children[0]
|
||||
self.childpage = self.child.screenUI()
|
||||
self.childfill = urwid.Filler(self.childpage, 'top', 22)
|
||||
self.childbox = urwid.BoxAdapter(self.childfill, 22)
|
||||
self.cols = urwid.Columns(
|
||||
[
|
||||
('fixed', 20, urwid.Pile([
|
||||
urwid.AttrMap(self.menubox, 'bright'),
|
||||
urwid.Divider(" ")])),
|
||||
('weight', 3, urwid.Pile([
|
||||
urwid.Divider(" "),
|
||||
self.childbox,
|
||||
urwid.Divider(" ")]))
|
||||
], 1)
|
||||
self.listwalker = urwid.SimpleListWalker([self.cols])
|
||||
#self.listwalker = urwid.TreeWalker([self.cols])
|
||||
self.listbox = urwid.ListBox(self.listwalker)
|
||||
#listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content))
|
||||
|
||||
self.frame = urwid.Frame(urwid.AttrWrap(self.listbox, 'body'),
|
||||
header=self.header, footer=self.footer)
|
||||
|
||||
palette = \
|
||||
[
|
||||
('body', 'black', 'light gray', 'standout'),
|
||||
('reverse', 'light gray', 'black'),
|
||||
('header', 'white', 'dark red', 'bold'),
|
||||
('important', 'dark blue', 'light gray',
|
||||
('standout', 'underline')),
|
||||
('editfc', 'white', 'dark blue', 'bold'),
|
||||
('editbx', 'light gray', 'dark blue'),
|
||||
('editcp', 'black', 'light gray', 'standout'),
|
||||
('bright', 'dark gray', 'light gray', ('bold', 'standout')),
|
||||
('buttn', 'black', 'dark cyan'),
|
||||
('buttnf', 'white', 'dark blue', 'bold'),
|
||||
('light gray', 'white', 'light gray', 'bold'),
|
||||
('red', 'dark red', 'light gray', 'bold'),
|
||||
('black', 'black', 'black', 'bold'),
|
||||
]
|
||||
|
||||
# use appropriate Screen class
|
||||
if urwid.web_display.is_web_request():
|
||||
self.screen = urwid.web_display.Screen()
|
||||
else:
|
||||
self.screen = urwid.raw_display.Screen()
|
||||
|
||||
def unhandled(key):
|
||||
if key == 'f8':
|
||||
raise urwid.ExitMainLoop()
|
||||
if key == 'shift tab':
|
||||
self.child.walker.tab_prev()
|
||||
if key == 'tab':
|
||||
self.child.walker.tab_next()
|
||||
|
||||
self.mainloop = urwid.MainLoop(self.frame, palette, self.screen,
|
||||
unhandled_input=unhandled)
|
||||
#Initialize each module completely before any events are handled
|
||||
for child in reversed(self.children):
|
||||
self.setChildScreen(name=child.name)
|
||||
|
||||
signal.signal(signal.SIGUSR1, self.handle_sigusr1)
|
||||
|
||||
dialog.display_dialog(
|
||||
self.child,
|
||||
widget.TextLabel("It is highly recommended to change default "
|
||||
"admin password."),
|
||||
"WARNING!")
|
||||
self.mainloop.run()
|
||||
|
||||
def exit_program(self, button):
|
||||
#return kernel logging to normal
|
||||
noout = open('/dev/null', 'w')
|
||||
subprocess.call(["sysctl", "-w", "kernel.printk=7 4 1 7"],
|
||||
stdout=noout, stderr=noout)
|
||||
#Fix /etc/hosts before quitting
|
||||
dnsobj = self.children[int(self.choices.index("DNS & Hostname"))]
|
||||
dnsobj.fixEtcHosts()
|
||||
|
||||
raise urwid.ExitMainLoop()
|
||||
|
||||
def handle_sigusr1(self, signum, stack):
|
||||
log.info("Received signal: %s" % signum)
|
||||
try:
|
||||
savetimeout = 60
|
||||
success, modulename = timeout.run_with_timeout(
|
||||
self.global_save, timeout=savetimeout,
|
||||
default=(False, "timeout"))
|
||||
if success:
|
||||
log.info("Save successful!")
|
||||
else:
|
||||
log.error("Save failed on module %s" % modulename)
|
||||
|
||||
except (KeyboardInterrupt, timeout.TimeoutError):
|
||||
log.exception("Save on signal timed out. Save not complete.")
|
||||
except Exception:
|
||||
log.exception("Save failed for unknown reason:")
|
||||
self.exit_program(None)
|
||||
|
||||
def global_save(self):
|
||||
#Runs save function for every module
|
||||
for module, modulename in zip(self.children, self.choices):
|
||||
#Run invisible modules. They may not have screen methods
|
||||
if not module.visible:
|
||||
try:
|
||||
module.apply(None)
|
||||
except Exception as e:
|
||||
log.error("Unable to save module %s: %s" % (modulename, e))
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
log.info("Checking and applying module: %s"
|
||||
% modulename)
|
||||
self.footer.set_text("Checking and applying module: %s"
|
||||
% modulename)
|
||||
self.refreshScreen()
|
||||
module.refresh()
|
||||
if module.apply(None):
|
||||
log.info("Saving module: %s" % modulename)
|
||||
else:
|
||||
return False, modulename
|
||||
except AttributeError as e:
|
||||
log.debug("Module %s does not have save function: %s"
|
||||
% (modulename, e))
|
||||
return True, None
|
||||
|
||||
|
||||
def setup():
|
||||
urwid.web_display.set_preferences("Fuel Setup")
|
||||
# try to handle short web requests quickly
|
||||
if urwid.web_display.handle_short_request():
|
||||
return
|
||||
FuelSetup()
|
||||
|
||||
|
||||
def save_only(iface, settingsfile='/etc/fuel/astute.yaml'):
|
||||
import common.network as network
|
||||
from common import pwgen
|
||||
import netifaces
|
||||
|
||||
#Calculate and set Static/DHCP pool fields
|
||||
#Max IPs = net size - 2 (master node + bcast)
|
||||
try:
|
||||
ip = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
|
||||
netmask = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['netmask']
|
||||
mac = netifaces.ifaddresses(iface)[netifaces.AF_LINK][0]['addr']
|
||||
except Exception:
|
||||
print("Interface %s is missing either IP address or netmask"
|
||||
% (iface))
|
||||
sys.exit(1)
|
||||
net_ip_list = network.getNetwork(ip, netmask)
|
||||
try:
|
||||
dhcp_pool = net_ip_list[1:]
|
||||
dynamic_start = str(dhcp_pool[0])
|
||||
dynamic_end = str(dhcp_pool[-1])
|
||||
except Exception:
|
||||
print("Unable to define DHCP pools")
|
||||
sys.exit(1)
|
||||
try:
|
||||
hostname, sep, domain = os.uname()[1].partition('.')
|
||||
except Exception:
|
||||
print("Unable to calculate hostname and domain")
|
||||
sys.exit(1)
|
||||
try:
|
||||
dhcptimeout = 5
|
||||
default = []
|
||||
with timeout.run_with_timeout(dhcp_checker.utils.IfaceState, [iface],
|
||||
timeout=dhcptimeout) as iface:
|
||||
dhcp_server_data = timeout.run_with_timeout(
|
||||
dhcp_checker.api.check_dhcp_on_eth,
|
||||
[iface, dhcptimeout], timeout=dhcptimeout,
|
||||
default=default)
|
||||
except (KeyboardInterrupt, timeout.TimeoutError):
|
||||
log.debug("DHCP scan timed out")
|
||||
log.warning(traceback.format_exc())
|
||||
dhcp_server_data = default
|
||||
|
||||
num_dhcp = len(dhcp_server_data)
|
||||
if num_dhcp == 0:
|
||||
log.debug("No DHCP servers found")
|
||||
else:
|
||||
#Problem exists, but permit user to continue
|
||||
log.error("%s foreign DHCP server(s) found: %s" %
|
||||
(num_dhcp, dhcp_server_data))
|
||||
print("ERROR: %s foreign DHCP server(s) found: %s" %
|
||||
(num_dhcp, dhcp_server_data))
|
||||
if network.duplicateIPExists(ip, iface):
|
||||
log.error("Duplicate host found with IP {0}".format(ip))
|
||||
print("ERROR: Duplicate host found with IP {0}".format(ip))
|
||||
|
||||
defaultsettingsfile = os.path.join(os.path.dirname(__file__),
|
||||
"settings.yaml")
|
||||
newsettings = Settings().read(settingsfile)
|
||||
settings = \
|
||||
{
|
||||
"ADMIN_NETWORK/interface": iface,
|
||||
"ADMIN_NETWORK/ipaddress": ip,
|
||||
"ADMIN_NETWORK/netmask": netmask,
|
||||
"ADMIN_NETWORK/mac": mac,
|
||||
"ADMIN_NETWORK/dhcp_pool_start": dynamic_start,
|
||||
"ADMIN_NETWORK/dhcp_pool_end": dynamic_end,
|
||||
"ADMIN_NETWORK/dhcp_gateway": ip,
|
||||
"HOSTNAME": hostname,
|
||||
"DNS_DOMAIN": domain,
|
||||
"DNS_SEARCH": domain,
|
||||
"astute/user": "naily",
|
||||
"astute/password": pwgen.password(),
|
||||
"cobbler/user": "cobbler",
|
||||
"cobbler/password": pwgen.password(),
|
||||
"keystone/admin_token": pwgen.password(),
|
||||
"keystone/ostf_user": "ostf",
|
||||
"keystone/ostf_password": pwgen.password(),
|
||||
"keystone/nailgun_user": "nailgun",
|
||||
"keystone/nailgun_password": pwgen.password(),
|
||||
"keystone/monitord_user": "monitord",
|
||||
"keystone/monitord_password": pwgen.password(),
|
||||
"mcollective/user": "mcollective",
|
||||
"mcollective/password": pwgen.password(),
|
||||
"postgres/keystone_dbname": "keystone",
|
||||
"postgres/keystone_user": "keystone",
|
||||
"postgres/keystone_password": pwgen.password(),
|
||||
"postgres/nailgun_dbname": "nailgun",
|
||||
"postgres/nailgun_user": "nailgun",
|
||||
"postgres/nailgun_password": pwgen.password(),
|
||||
"postgres/ostf_dbname": "ostf",
|
||||
"postgres/ostf_user": "ostf",
|
||||
"postgres/ostf_password": pwgen.password(),
|
||||
"FUEL_ACCESS/user": "admin",
|
||||
"FUEL_ACCESS/password": "admin",
|
||||
}
|
||||
for setting in settings.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings.keys():
|
||||
newsettings[part1] = {}
|
||||
#Keep old values for passwords if already set
|
||||
if "password" in setting:
|
||||
newsettings[part1].setdefault(part2, settings[setting])
|
||||
else:
|
||||
newsettings[part1][part2] = settings[setting]
|
||||
else:
|
||||
if "password" in setting:
|
||||
newsettings.setdefault(setting, settings[setting])
|
||||
else:
|
||||
newsettings[setting] = settings[setting]
|
||||
|
||||
#Write astute.yaml
|
||||
Settings().write(newsettings, defaultsfile=defaultsettingsfile,
|
||||
outfn=settingsfile)
|
||||
|
||||
|
||||
def main(*args, **kwargs):
|
||||
if urwid.VERSION < (1, 1, 0):
|
||||
print("This program requires urwid 1.1.0 or greater.")
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option("-s", "--save-only", dest="save_only",
|
||||
action="store_true",
|
||||
help="Save default values and exit.")
|
||||
|
||||
parser.add_option("-i", "--iface", dest="iface", metavar="IFACE",
|
||||
default="eth0", help="Set IFACE as primary.")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.save_only:
|
||||
save_only(options.iface)
|
||||
else:
|
||||
setup()
|
||||
|
||||
if '__main__' == __name__ or urwid.web_display.is_web_request():
|
||||
setup()
|
|
@ -1,281 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2015 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.
|
||||
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import url_access_checker.api as urlck
|
||||
import url_access_checker.errors as url_errors
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.mirrors')
|
||||
blank = urwid.Divider()
|
||||
|
||||
VERSION_YAML_FILE = '/etc/nailgun/version.yaml'
|
||||
FUEL_BOOTSTRAP_IMAGE_CONF = '/etc/fuel-bootstrap-image.conf'
|
||||
BOOTSTRAP_FLAVOR_KEY = 'BOOTSTRAP/flavor'
|
||||
MOS_REPO_DFLT = 'http://mirror.fuel-infra.org/mos-repos/ubuntu/{mos_version}'
|
||||
|
||||
|
||||
class bootstrapimg(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "Bootstrap Image"
|
||||
self.priority = 55
|
||||
self.visible = True
|
||||
self.deployment = "pre"
|
||||
self.parent = parent
|
||||
self.distro = 'ubuntu'
|
||||
self._distro_release = None
|
||||
self._mos_version = None
|
||||
self._bootstrap_flavor = None
|
||||
|
||||
# UI Text
|
||||
self.header_content = ["Bootstrap image configuration"]
|
||||
fields = (
|
||||
'flavor',
|
||||
'MIRROR_DISTRO',
|
||||
'MIRROR_MOS',
|
||||
'HTTP_PROXY',
|
||||
'EXTRA_DEB_REPOS')
|
||||
self.fields = ['BOOTSTRAP/{0}'.format(var) for var in fields]
|
||||
# TODO(asheplyakov):
|
||||
# switch to the new MOS APT repo structure when it's ready
|
||||
mos_repo_dflt = MOS_REPO_DFLT.format(mos_version=self.mos_version)
|
||||
self.defaults = {
|
||||
BOOTSTRAP_FLAVOR_KEY: {
|
||||
"label": "Flavor",
|
||||
"tooltip": "",
|
||||
"value": "radio",
|
||||
"choices": ["CentOS", "Ubuntu (EXPERIMENTAL)"]},
|
||||
"BOOTSTRAP/MIRROR_DISTRO": {
|
||||
"label": "Ubuntu mirror",
|
||||
"tooltip": "Ubuntu APT repo URL",
|
||||
"value": "http://archive.ubuntu.com/ubuntu"},
|
||||
"BOOTSTRAP/MIRROR_MOS": {
|
||||
"label": "MOS mirror",
|
||||
"tooltip": ("MOS APT repo URL (can use file:// protocol, will"
|
||||
"use local mirror in such case"),
|
||||
"value": mos_repo_dflt},
|
||||
"BOOTSTRAP/HTTP_PROXY": {
|
||||
"label": "HTTP proxy",
|
||||
"tooltip": "Use this proxy when building the bootstrap image",
|
||||
"value": ""},
|
||||
"BOOTSTRAP/EXTRA_DEB_REPOS": {
|
||||
"label": "Extra APT repositories",
|
||||
"tooltip": "Additional repositories for bootstrap image",
|
||||
"value": ""}
|
||||
}
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
|
||||
def _read_version_info(self):
|
||||
settings = Settings()
|
||||
dat = settings.read(VERSION_YAML_FILE)
|
||||
version_info = dat['VERSION']
|
||||
self._mos_version = version_info['release']
|
||||
self._distro_release = version_info.get('ubuntu_release', 'trusty')
|
||||
|
||||
@property
|
||||
def mos_version(self):
|
||||
if not self._mos_version:
|
||||
self._read_version_info()
|
||||
return self._mos_version
|
||||
|
||||
@property
|
||||
def distro_release(self):
|
||||
if not self._distro_release:
|
||||
self._read_version_info()
|
||||
return self._distro_release
|
||||
|
||||
@property
|
||||
def responses(self):
|
||||
ret = dict()
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == 'blank':
|
||||
pass
|
||||
elif fieldname == BOOTSTRAP_FLAVOR_KEY:
|
||||
rb_group = self.edits[index].rb_group
|
||||
flavor = 'centos' if rb_group[0].state else 'ubuntu'
|
||||
ret[fieldname] = flavor
|
||||
else:
|
||||
ret[fieldname] = self.edits[index].get_edit_text()
|
||||
return ret
|
||||
|
||||
def check(self, args):
|
||||
"""Validate that all fields have valid values through sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
responses = self.responses
|
||||
|
||||
errors = []
|
||||
if responses.get(BOOTSTRAP_FLAVOR_KEY) == 'ubuntu':
|
||||
errors.extend(self.check_apt_repos(responses))
|
||||
|
||||
if errors:
|
||||
self.parent.footer.set_text("Error: %s" % (errors[0]))
|
||||
log.error("Errors: %s", errors)
|
||||
return False
|
||||
else:
|
||||
self.parent.footer.set_text("No errors found.")
|
||||
return responses
|
||||
|
||||
def check_apt_repos(self, responses):
|
||||
errors = []
|
||||
# APT repo URL must not be empty
|
||||
distro_repo_base = responses['BOOTSTRAP/MIRROR_DISTRO'].strip()
|
||||
mos_repo_base = responses['BOOTSTRAP/MIRROR_MOS'].strip()
|
||||
http_proxy = responses['BOOTSTRAP/HTTP_PROXY'].strip()
|
||||
|
||||
if len(distro_repo_base) == 0:
|
||||
errors.append("Ubuntu mirror URL must not be empty.")
|
||||
|
||||
if not self.checkDistroRepo(distro_repo_base, http_proxy):
|
||||
errors.append("Ubuntu repository is not accessible.")
|
||||
|
||||
if len(mos_repo_base) == 0:
|
||||
errors.append("MOS repo URL must not be empty.")
|
||||
|
||||
if not self.checkMOSRepo(mos_repo_base, http_proxy):
|
||||
errors.append("MOS repository is not accessible.")
|
||||
|
||||
return errors
|
||||
|
||||
def apply(self, args):
|
||||
responses = self.check(args)
|
||||
if responses is False:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
return False
|
||||
|
||||
with open(FUEL_BOOTSTRAP_IMAGE_CONF, "w") as fbiconf:
|
||||
for var in self.fields:
|
||||
scope, name = var.split('/')
|
||||
fbiconf.write('{0}="{1}"\n'.format(name, responses.get(var)))
|
||||
fbiconf.write('MOS_VERSION="{0}"'.format(self.mos_version))
|
||||
self.save(responses)
|
||||
return True
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def _ui_set_bootstrap_flavor(self):
|
||||
rb_index = self.fields.index(BOOTSTRAP_FLAVOR_KEY)
|
||||
is_ubuntu = self._bootstrap_flavor is not None and \
|
||||
'ubuntu' in self._bootstrap_flavor
|
||||
try:
|
||||
rb_group = self.edits[rb_index].rb_group
|
||||
rb_group[0].set_state(not is_ubuntu)
|
||||
rb_group[1].set_state(is_ubuntu)
|
||||
except AttributeError:
|
||||
# the UI hasn't been initalized yet
|
||||
pass
|
||||
|
||||
def _set_bootstrap_flavor(self, flavor):
|
||||
is_ubuntu = flavor is not None and 'ubuntu' in flavor.lower()
|
||||
self._bootstrap_flavor = 'ubuntu' if is_ubuntu else 'centos'
|
||||
self._ui_set_bootstrap_flavor()
|
||||
|
||||
def load(self):
|
||||
# Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
|
||||
for setting in self.defaults:
|
||||
try:
|
||||
if BOOTSTRAP_FLAVOR_KEY == setting:
|
||||
section, key = BOOTSTRAP_FLAVOR_KEY.split('/')
|
||||
flavor = oldsettings[section][key]
|
||||
self._set_bootstrap_flavor(flavor)
|
||||
elif "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
except KeyError:
|
||||
log.warning("no setting named %s found.", setting)
|
||||
except Exception as e:
|
||||
log.warning("unexpected error: %s", e.message)
|
||||
return oldsettings
|
||||
|
||||
def save(self, responses):
|
||||
# Generic settings start
|
||||
newsettings = dict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
# We may not touch all settings, so copy oldsettings first
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
# Generic settings end
|
||||
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
# Set oldsettings to reflect new settings
|
||||
self.oldsettings = newsettings
|
||||
# Update self.defaults
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank":
|
||||
log.info("resetting %s", fieldname)
|
||||
if fieldname not in self.defaults.keys():
|
||||
log.error("no such field: %s, valid are %s",
|
||||
fieldname, ' '.join(self.defaults.keys()))
|
||||
continue
|
||||
if fieldname not in newsettings.keys():
|
||||
log.error("newsettings: no such field: %s, valid are %s",
|
||||
fieldname, ' '.join(newsettings.keys()))
|
||||
continue
|
||||
self.defaults[fieldname]['value'] = newsettings[fieldname]
|
||||
|
||||
def check_url(self, url, http_proxy):
|
||||
try:
|
||||
return urlck.check_urls([url], proxies={'http': http_proxy})
|
||||
except url_errors.UrlNotAvailable:
|
||||
return False
|
||||
|
||||
def checkDistroRepo(self, base_url, http_proxy):
|
||||
release_url = '{base_url}/dists/{distro_release}/Release'.format(
|
||||
base_url=base_url, distro_release=self.distro_release)
|
||||
available = self.check_url(release_url, http_proxy)
|
||||
# TODO(asheplyakov):
|
||||
# check if it's possible to debootstrap with this repo
|
||||
return available
|
||||
|
||||
def checkMOSRepo(self, base_url, http_proxy):
|
||||
# deb {repo_base_url}/mos/ubuntu mos{mos_version} main
|
||||
codename = 'mos{0}'.format(self.mos_version)
|
||||
release_url = '{base_url}/dists/{codename}/Release'.format(
|
||||
base_url=base_url, codename=codename)
|
||||
available = self.check_url(release_url, http_proxy)
|
||||
return available
|
||||
|
||||
def radioSelect(self, current, state, user_data=None):
|
||||
pass
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
screen = ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
||||
# set the radiobutton state (ModuleHelper handles only yes/no choice)
|
||||
self._ui_set_bootstrap_flavor()
|
||||
return screen
|
|
@ -1,437 +0,0 @@
|
|||
#!/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.modulehelper import ModuleHelper
|
||||
from fuelmenu.common import network
|
||||
from fuelmenu.common import timeout
|
||||
import fuelmenu.common.urwidwrapper as widget
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import netaddr
|
||||
import traceback
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.pxe_setup')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class cobblerconf(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "PXE Setup"
|
||||
self.priority = 20
|
||||
self.visible = True
|
||||
self.netsettings = dict()
|
||||
self.parent = parent
|
||||
self.deployment = "pre"
|
||||
self.getNetwork()
|
||||
self.gateway = self.get_default_gateway_linux()
|
||||
self.activeiface = sorted(self.netsettings.keys())[0]
|
||||
self.parent.managediface = self.activeiface
|
||||
|
||||
#UI text
|
||||
text1 = "Settings for PXE booting of slave nodes."
|
||||
text2 = "Select the interface where PXE will run:"
|
||||
#Placeholder for network settings text
|
||||
self.net_choices = widget.ChoicesGroup(sorted(self.netsettings.keys()),
|
||||
default_value=self.activeiface,
|
||||
fn=self.radioSelect)
|
||||
self.net_text1 = widget.TextLabel("")
|
||||
self.net_text2 = widget.TextLabel("")
|
||||
self.net_text3 = widget.TextLabel("")
|
||||
self.net_text4 = widget.TextLabel("")
|
||||
self.header_content = [text1, text2, self.net_choices, self.net_text1,
|
||||
self.net_text2, self.net_text3, self.net_text4]
|
||||
self.fields = ["dynamic_label", "ADMIN_NETWORK/dhcp_pool_start",
|
||||
"ADMIN_NETWORK/dhcp_pool_end",
|
||||
"ADMIN_NETWORK/dhcp_gateway"]
|
||||
|
||||
self.defaults = \
|
||||
{
|
||||
"ADMIN_NETWORK/dhcp_pool_start": {"label": "DHCP Pool Start",
|
||||
"tooltip": "Used for \
|
||||
defining IPs for hosts and instance public addresses",
|
||||
"value": "10.0.0.3"},
|
||||
"ADMIN_NETWORK/dhcp_pool_end": {"label": "DHCP Pool End",
|
||||
"tooltip": "Used for defining \
|
||||
IPs for hosts and instance public addresses",
|
||||
"value": "10.0.0.254"},
|
||||
"ADMIN_NETWORK/dhcp_gateway": {"label": "DHCP Gateway",
|
||||
"tooltip": "Default gateway \
|
||||
to advertise via DHCP to nodes",
|
||||
"value": "10.0.0.2"},
|
||||
"dynamic_label": {"label": "DHCP pool for node discovery:",
|
||||
"tooltip": "",
|
||||
"value": "label"},
|
||||
}
|
||||
|
||||
self.extdhcp = True
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
|
||||
def check(self, args):
|
||||
"""Validates all fields have valid values and some sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
|
||||
#Refresh networking to make sure IP matches
|
||||
self.getNetwork()
|
||||
|
||||
#Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank" and "label" not in fieldname:
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
|
||||
###Validate each field
|
||||
errors = []
|
||||
|
||||
#Set internal_{ipaddress,netmask,interface}
|
||||
responses["ADMIN_NETWORK/interface"] = self.activeiface
|
||||
responses["ADMIN_NETWORK/netmask"] = self.netsettings[
|
||||
self.activeiface]["netmask"]
|
||||
responses["ADMIN_NETWORK/mac"] = self.netsettings[
|
||||
self.activeiface]["mac"]
|
||||
responses["ADMIN_NETWORK/ipaddress"] = self.netsettings[
|
||||
self.activeiface]["addr"]
|
||||
|
||||
#ensure management interface is valid
|
||||
if responses["ADMIN_NETWORK/interface"] not in self.netsettings.keys():
|
||||
errors.append("Management interface not valid")
|
||||
else:
|
||||
self.parent.footer.set_text("Scanning for DHCP servers. \
|
||||
Please wait...")
|
||||
self.parent.refreshScreen()
|
||||
|
||||
###Start DHCP check on this interface
|
||||
#dhcp_server_data=[{'server_id': '192.168.200.2', 'iface': 'eth2',
|
||||
# 'yiaddr': '192.168.200.15', 'mac':
|
||||
# '52:54:00:12:35:02', 'server_ip': '192.168.200.2',
|
||||
# 'dport': 67, 'message': 'offer',
|
||||
# 'gateway': '0.0.0.0'}]
|
||||
try:
|
||||
dhcptimeout = 5
|
||||
default = []
|
||||
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,
|
||||
default=default)
|
||||
except (KeyboardInterrupt, timeout.TimeoutError):
|
||||
log.debug("DHCP scan timed out")
|
||||
log.warning(traceback.format_exc())
|
||||
dhcp_server_data = default
|
||||
|
||||
num_dhcp = len(dhcp_server_data)
|
||||
if num_dhcp == 0:
|
||||
log.debug("No DHCP servers found")
|
||||
else:
|
||||
#Problem exists, but permit user to continue
|
||||
log.error("%s foreign DHCP server(s) found: %s" %
|
||||
(num_dhcp, dhcp_server_data))
|
||||
|
||||
#Build dialog elements
|
||||
dhcp_info = []
|
||||
dhcp_info.append(urwid.Padding(
|
||||
urwid.Text(("header", "!!! WARNING !!!")),
|
||||
"center"))
|
||||
dhcp_info.append(widget.TextLabel("You have selected an \
|
||||
interface that contains one or more DHCP servers. This will impact \
|
||||
provisioning. You should disable these DHCP servers before you continue, or \
|
||||
else deployment will likely fail."))
|
||||
dhcp_info.append(widget.TextLabel(""))
|
||||
for index, dhcp_server in enumerate(dhcp_server_data):
|
||||
dhcp_info.append(widget.TextLabel("DHCP Server #%s:" %
|
||||
(index + 1)))
|
||||
dhcp_info.append(widget.TextLabel("IP address: %-10s" %
|
||||
dhcp_server['server_ip']))
|
||||
dhcp_info.append(widget.TextLabel("MAC address: %-10s" %
|
||||
dhcp_server['mac']))
|
||||
dhcp_info.append(widget.TextLabel(""))
|
||||
dialog.display_dialog(self, urwid.Pile(dhcp_info),
|
||||
"DHCP Servers Found on %s"
|
||||
% self.activeiface)
|
||||
###Ensure pool start and end are on the same subnet as mgmt_if
|
||||
#Ensure mgmt_if has an IP first
|
||||
if len(self.netsettings[responses[
|
||||
"ADMIN_NETWORK/interface"]]["addr"]) == 0:
|
||||
errors.append("Go to Interfaces to configure management \
|
||||
interface first.")
|
||||
else:
|
||||
#Ensure ADMIN_NETWORK/interface is not running DHCP
|
||||
if self.netsettings[responses[
|
||||
"ADMIN_NETWORK/interface"]]["bootproto"] == "dhcp":
|
||||
errors.append("%s is running DHCP. Change it to static "
|
||||
"first." % self.activeiface)
|
||||
#Ensure DHCP Pool Start and DHCP Pool are valid IPs
|
||||
try:
|
||||
if netaddr.valid_ipv4(responses[
|
||||
"ADMIN_NETWORK/dhcp_pool_start"]):
|
||||
dhcp_start = netaddr.IPAddress(
|
||||
responses["ADMIN_NETWORK/dhcp_pool_start"])
|
||||
if not dhcp_start:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
else:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
except Exception:
|
||||
errors.append("Invalid IP address for DHCP Pool Start")
|
||||
try:
|
||||
if netaddr.valid_ipv4(responses[
|
||||
"ADMIN_NETWORK/dhcp_gateway"]):
|
||||
dhcp_gateway = netaddr.IPAddress(
|
||||
responses["ADMIN_NETWORK/dhcp_gateway"])
|
||||
if not dhcp_gateway:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
else:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
except Exception:
|
||||
errors.append("Invalid IP address for DHCP Gateway")
|
||||
|
||||
try:
|
||||
if netaddr.valid_ipv4(responses[
|
||||
"ADMIN_NETWORK/dhcp_pool_end"]):
|
||||
dhcp_end = netaddr.IPAddress(
|
||||
responses["ADMIN_NETWORK/dhcp_pool_end"])
|
||||
if not dhcp_end:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
else:
|
||||
raise BadIPException("Not a valid IP address")
|
||||
except Exception:
|
||||
errors.append("Invalid IP address for DHCP Pool end")
|
||||
|
||||
#Ensure pool start and end are in the same subnet of each other
|
||||
netmask = self.netsettings[responses[
|
||||
"ADMIN_NETWORK/interface"
|
||||
]]["netmask"]
|
||||
if not network.inSameSubnet(
|
||||
responses["ADMIN_NETWORK/dhcp_pool_start"],
|
||||
responses["ADMIN_NETWORK/dhcp_pool_end"], netmask):
|
||||
errors.append("DHCP Pool start and end are not in the "
|
||||
"same subnet.")
|
||||
|
||||
#Ensure pool start and end are in the right netmask
|
||||
mgmt_if_ipaddr = self.netsettings[responses[
|
||||
"ADMIN_NETWORK/interface"]]["addr"]
|
||||
if network.inSameSubnet(responses[
|
||||
"ADMIN_NETWORK/dhcp_pool_start"],
|
||||
mgmt_if_ipaddr, netmask) is False:
|
||||
errors.append("DHCP Pool start does not match management"
|
||||
" network.")
|
||||
if network.inSameSubnet(responses[
|
||||
"ADMIN_NETWORK/dhcp_pool_end"],
|
||||
mgmt_if_ipaddr, netmask) is False:
|
||||
errors.append("DHCP Pool end does not match management "
|
||||
"network.")
|
||||
|
||||
if network.inSameSubnet(responses[
|
||||
"ADMIN_NETWORK/dhcp_gateway"],
|
||||
mgmt_if_ipaddr, netmask) is False:
|
||||
errors.append("DHCP Gateway does not match management "
|
||||
"network.")
|
||||
|
||||
self.parent.footer.set_text("Scanning for duplicate IP address"
|
||||
"es. Please wait...")
|
||||
# Bind arping to mgmt_if_ipaddr if it assigned
|
||||
assigned_ips = [v.get('addr') for v in
|
||||
self.netsettings.itervalues()]
|
||||
arping_bind = mgmt_if_ipaddr in assigned_ips
|
||||
if network.duplicateIPExists(mgmt_if_ipaddr, self.activeiface,
|
||||
arping_bind):
|
||||
errors.append("Duplicate host found with IP {0}.".format(
|
||||
mgmt_if_ipaddr))
|
||||
|
||||
if len(errors) > 0:
|
||||
self.parent.footer.set_text("Error: %s" % (errors[0]))
|
||||
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:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
return False
|
||||
|
||||
#Always save even if "post"
|
||||
self.save(responses)
|
||||
return True
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
self.setNetworkDetails()
|
||||
|
||||
def load(self):
|
||||
#Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings.copy()
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
log.debug("Old settings %s" % oldsettings)
|
||||
for setting in self.defaults.keys():
|
||||
if "label" in setting:
|
||||
continue
|
||||
elif "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
if oldsettings["ADMIN_NETWORK"]["interface"] \
|
||||
in self.netsettings.keys():
|
||||
self.activeiface = oldsettings["ADMIN_NETWORK"]["interface"]
|
||||
return oldsettings
|
||||
|
||||
def save(self, responses):
|
||||
## Generic settings start ##
|
||||
newsettings = ModuleHelper.save(self, responses)
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
## Generic settings end ##
|
||||
|
||||
## Need to calculate and netmask
|
||||
newsettings['ADMIN_NETWORK']['netmask'] = \
|
||||
self.netsettings[newsettings['ADMIN_NETWORK']['interface']][
|
||||
"netmask"]
|
||||
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
#Set oldsettings to reflect new settings
|
||||
self.oldsettings = newsettings
|
||||
#Update self.defaults
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank" and "label" not in fieldname:
|
||||
self.defaults[fieldname]['value'] = responses[fieldname]
|
||||
|
||||
self.parent.footer.set_text("Changes saved successfully.")
|
||||
|
||||
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 radioSelect(self, current, state, user_data=None):
|
||||
"""Update network details and display information."""
|
||||
### Urwid returns the previously selected radio button.
|
||||
### The previous object has True state, which is wrong.
|
||||
### Somewhere in rb group a RadioButton is set to True.
|
||||
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()
|
||||
self.parent.managediface = self.activeiface
|
||||
break
|
||||
self.gateway = self.get_default_gateway_linux()
|
||||
self.getNetwork()
|
||||
self.setNetworkDetails()
|
||||
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))
|
||||
log.debug("bootproto for %s: %s" % (self.netsettings[self.activeiface],
|
||||
self.netsettings[self.activeiface]['bootproto']))
|
||||
if self.netsettings[self.activeiface]['link'].upper() == "UP":
|
||||
if self.netsettings[self.activeiface]['bootproto'] == "dhcp":
|
||||
self.net_text4.set_text("WARNING: Cannot use interface running"
|
||||
" DHCP.\nReconfigure as static in "
|
||||
"Network Setup screen.")
|
||||
else:
|
||||
self.net_text4.set_text("")
|
||||
else:
|
||||
self.net_text4.set_text("WARNING: This interface is DOWN. "
|
||||
"Configure it first.")
|
||||
|
||||
#If DHCP pool start and matches activeiface network, don't update
|
||||
#This means if you change your pool values, go to another page, then
|
||||
#go back, it will not reset your changes. But what is more likely is
|
||||
#you will change the network settings for interface eth0 and then come
|
||||
#back to this page to update your DHCP settings. If the inSameSubnet
|
||||
#test fails, just recalculate and set new values.
|
||||
for index, key in enumerate(self.fields):
|
||||
if key == "ADMIN_NETWORK/dhcp_pool_start":
|
||||
dhcp_start = self.edits[index].get_edit_text()
|
||||
break
|
||||
if network.inSameSubnet(dhcp_start,
|
||||
self.netsettings[self.activeiface]['addr'],
|
||||
self.netsettings[self.activeiface]['netmask']):
|
||||
log.debug("Existing network settings exist. Not changing.")
|
||||
return
|
||||
else:
|
||||
log.debug("Existing network settings missing or invalid. "
|
||||
"Updating...")
|
||||
|
||||
#Calculate and set Static/DHCP pool fields
|
||||
#Max IPs = net size - 2 (master node + bcast)
|
||||
#Add gateway so we exclude it
|
||||
net_ip_list = network.getNetwork(
|
||||
self.netsettings[self.activeiface]['addr'],
|
||||
self.netsettings[self.activeiface]['netmask'],
|
||||
self.gateway)
|
||||
try:
|
||||
dhcp_pool = net_ip_list[1:]
|
||||
dynamic_start = str(dhcp_pool[0])
|
||||
dynamic_end = str(dhcp_pool[-1])
|
||||
if self.net_text4.get_text() == "":
|
||||
self.net_text4.set_text("This network configuration can "
|
||||
"support %s nodes." % len(dhcp_pool))
|
||||
except Exception:
|
||||
#We don't have valid values, so mark all fields empty
|
||||
dynamic_start = ""
|
||||
dynamic_end = ""
|
||||
for index, key in enumerate(self.fields):
|
||||
if key == "ADMIN_NETWORK/dhcp_pool_start":
|
||||
self.edits[index].set_edit_text(dynamic_start)
|
||||
elif key == "ADMIN_NETWORK/dhcp_pool_end":
|
||||
self.edits[index].set_edit_text(dynamic_end)
|
||||
elif key == "ADMIN_NETWORK/dhcp_gateway":
|
||||
self.edits[index].set_edit_text(self.netsettings[
|
||||
self.activeiface]['addr'])
|
||||
|
||||
def refresh(self):
|
||||
self.getNetwork()
|
||||
self.setNetworkDetails()
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,393 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
from fuelmenu.common import dialog
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
from fuelmenu.common import replace
|
||||
import fuelmenu.common.urwidwrapper as widget
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import netaddr
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.mirrors')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class dnsandhostname(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "DNS & Hostname"
|
||||
self.priority = 50
|
||||
self.visible = True
|
||||
self.netsettings = dict()
|
||||
self.deployment = "pre"
|
||||
self.getNetwork()
|
||||
self.gateway = self.get_default_gateway_linux()
|
||||
self.extdhcp = True
|
||||
self.parent = parent
|
||||
|
||||
#UI Text
|
||||
self.header_content = ["DNS and hostname setup", "Note: Leave "
|
||||
"External DNS blank if you do not have "
|
||||
"Internet access."]
|
||||
self.fields = ["HOSTNAME", "DNS_DOMAIN", "DNS_SEARCH", "DNS_UPSTREAM",
|
||||
"blank", "TEST_DNS"]
|
||||
hostname, sep, domain = os.uname()[1].partition('.')
|
||||
self.defaults = \
|
||||
{
|
||||
"HOSTNAME": {"label": "Hostname",
|
||||
"tooltip": "Hostname to use for Fuel master node",
|
||||
"value": hostname},
|
||||
"DNS_UPSTREAM": {"label": "External DNS",
|
||||
"tooltip": "DNS server(s) (comma separated) \
|
||||
to handle DNS requests (example 8.8.8.8,8.8.4.4)",
|
||||
"value": "8.8.8.8"},
|
||||
"DNS_DOMAIN": {"label": "Domain",
|
||||
"tooltip": "Domain suffix to user for all \
|
||||
nodes in your cluster",
|
||||
"value": domain},
|
||||
"DNS_SEARCH": {"label": "Search Domain",
|
||||
"tooltip": "Domains to search when looking up \
|
||||
DNS (space separated)",
|
||||
"value": domain},
|
||||
"TEST_DNS": {"label": "Hostname to test DNS:",
|
||||
"value": "www.google.com",
|
||||
"tooltip": "DNS record to resolve to see if DNS \
|
||||
is accessible"}
|
||||
}
|
||||
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
self.fixEtcHosts()
|
||||
|
||||
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 through sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
#Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "blank":
|
||||
pass
|
||||
else:
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
|
||||
###Validate each field
|
||||
errors = []
|
||||
|
||||
#hostname must be under 60 chars
|
||||
if len(responses["HOSTNAME"]) >= 60:
|
||||
errors.append("Hostname must be under 60 chars.")
|
||||
|
||||
#hostname must not be empty
|
||||
if len(responses["HOSTNAME"]) == 0:
|
||||
errors.append("Hostname must not be empty.")
|
||||
|
||||
#hostname needs to have valid chars
|
||||
if re.search('[^a-z0-9-]', responses["HOSTNAME"]):
|
||||
errors.append(
|
||||
"Hostname must contain only alphanumeric and hyphen.")
|
||||
|
||||
#domain must be under 180 chars
|
||||
if len(responses["DNS_DOMAIN"]) >= 180:
|
||||
errors.append("Domain must be under 180 chars.")
|
||||
|
||||
#domain must not be empty
|
||||
if len(responses["DNS_DOMAIN"]) == 0:
|
||||
errors.append("Domain must not be empty.")
|
||||
|
||||
#domain needs to have valid chars
|
||||
if re.match('[^a-z0-9-.]', responses["DNS_DOMAIN"]):
|
||||
errors.append(
|
||||
"Domain must contain only alphanumeric, period and hyphen.")
|
||||
#ensure external DNS is valid
|
||||
if len(responses["DNS_UPSTREAM"]) == 0:
|
||||
#We will allow empty if user doesn't need external networking
|
||||
#and present a strongly worded warning
|
||||
msg = "If you continue without DNS, you may not be able to access"\
|
||||
+ " external data necessary for installation needed for " \
|
||||
+ "some OpenStack Releases."
|
||||
|
||||
dialog.display_dialog(
|
||||
self, widget.TextLabel(msg), "Empty DNS Warning")
|
||||
|
||||
else:
|
||||
#external DNS must contain only numbers, periods, and commas
|
||||
#Needs more serious ip address checking
|
||||
if re.match('[^0-9.,]', responses["DNS_UPSTREAM"]):
|
||||
errors.append(
|
||||
"External DNS must contain only IP addresses and commas.")
|
||||
|
||||
admin_ip = self.netsettings[self.parent.managediface]['addr']
|
||||
if admin_ip in responses["DNS_UPSTREAM"]:
|
||||
errors.append("Admin interface IP cannot be in upstream "
|
||||
"nameservers.")
|
||||
|
||||
#ensure test DNS name isn't empty
|
||||
if len(responses["TEST_DNS"]) == 0:
|
||||
errors.append("Test DNS must not be empty.")
|
||||
#Validate first IP address
|
||||
try:
|
||||
if netaddr.valid_ipv4(responses["DNS_UPSTREAM"].split(",")[0]):
|
||||
DNS_UPSTREAM = responses["DNS_UPSTREAM"].split(",")[0]
|
||||
else:
|
||||
errors.append("Not a valid IP address for External DNS: %s"
|
||||
% responses["DNS_UPSTREAM"])
|
||||
|
||||
#Try to resolve with first address
|
||||
if not self.checkDNS(DNS_UPSTREAM):
|
||||
#Warn user that DNS resolution failed, but continue
|
||||
msg = "Unable to resolve %s.\n\n" % responses['TEST_DNS']\
|
||||
+ "Possible causes for DNS failure include:\n"\
|
||||
+ "* Invalid DNS server\n"\
|
||||
+ "* Invalid gateway\n"\
|
||||
+ "* Other networking issue\n\n"\
|
||||
+ "Fuel Setup can save this configuration, but "\
|
||||
+ "you may want to correct your settings."
|
||||
dialog.display_dialog(self, widget.TextLabel(msg),
|
||||
"DNS Failure Warning")
|
||||
self.parent.refreshScreen()
|
||||
except Exception:
|
||||
errors.append("Not a valid IP address for External DNS: %s"
|
||||
% responses["DNS_UPSTREAM"])
|
||||
|
||||
if len(errors) > 0:
|
||||
self.parent.footer.set_text("Error: %s" % (errors[0]))
|
||||
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:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
return False
|
||||
|
||||
self.save(responses)
|
||||
#Update network details so we write correct IP address
|
||||
self.getNetwork()
|
||||
#Apply hostname
|
||||
expr = 'HOSTNAME=.*'
|
||||
replace.replaceInFile("/etc/sysconfig/network", expr,
|
||||
"HOSTNAME=%s.%s"
|
||||
% (responses["HOSTNAME"],
|
||||
responses["DNS_DOMAIN"]))
|
||||
#remove old hostname from /etc/hosts
|
||||
f = open("/etc/hosts", "r")
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
with open("/etc/hosts", "w") as etchosts:
|
||||
for line in lines:
|
||||
if "localhost" in line:
|
||||
etchosts.write(line)
|
||||
elif responses["HOSTNAME"] in line \
|
||||
or self.oldsettings["HOSTNAME"] \
|
||||
or self.netsettings[self.parent.managediface]['addr'] \
|
||||
in line:
|
||||
continue
|
||||
else:
|
||||
etchosts.write(line)
|
||||
etchosts.close()
|
||||
|
||||
#append hostname and ip address to /etc/hosts
|
||||
with open("/etc/hosts", "a") as etchosts:
|
||||
if self.netsettings[self.parent.managediface]["addr"] != "":
|
||||
managediface_ip = self.netsettings[
|
||||
self.parent.managediface]["addr"]
|
||||
else:
|
||||
managediface_ip = "127.0.0.1"
|
||||
etchosts.write(
|
||||
"%s %s.%s %s\n" % (managediface_ip, responses["HOSTNAME"],
|
||||
responses['DNS_DOMAIN'],
|
||||
responses["HOSTNAME"]))
|
||||
etchosts.close()
|
||||
|
||||
def make_resolv_conf(filename):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("search %s\n" % responses['DNS_SEARCH'])
|
||||
f.write("domain %s\n" % responses['DNS_DOMAIN'])
|
||||
for upstream_dns in responses['DNS_UPSTREAM'].split(','):
|
||||
f.write("nameserver %s\n" % upstream_dns)
|
||||
|
||||
# Create a temporary resolv.conf so DNS works before the cobbler
|
||||
# container is up and running.
|
||||
# TODO(asheplyakov): puppet does a similar thing, perhaps we can
|
||||
# use the corresponding template instead of duplicating it here.
|
||||
make_resolv_conf('/etc/resolv.conf')
|
||||
|
||||
return True
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def load(self):
|
||||
# Precedence of DNS information:
|
||||
# Class defaults, fuelmenu default YAML, astute.yaml, uname,
|
||||
# /etc/resolv.conf
|
||||
|
||||
#Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
|
||||
oldsettings = Settings().read(self.parent.settingsfile)
|
||||
|
||||
#Read hostname if it's already set
|
||||
try:
|
||||
hostname, sep, domain = os.uname()[1].partition('.')
|
||||
oldsettings["HOSTNAME"] = hostname
|
||||
oldsettings["DNS_DOMAIN"] = domain
|
||||
oldsettings["DNS_SEARCH"] = domain
|
||||
except Exception:
|
||||
log.warning("Unable to look up system hostname")
|
||||
|
||||
# Parse /etc/resolv.conf if it contains data
|
||||
search, domain, nameservers = self.getDNS()
|
||||
if search:
|
||||
oldsettings["DNS_SEARCH"] = search
|
||||
if domain:
|
||||
oldsettings["DNS_DOMAIN"] = domain
|
||||
if nameservers:
|
||||
oldsettings["DNS_UPSTREAM"] = nameservers
|
||||
|
||||
for setting in self.defaults.keys():
|
||||
try:
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
except Exception:
|
||||
log.warning("No setting named %s found." % setting)
|
||||
continue
|
||||
return oldsettings
|
||||
|
||||
def getDNS(self, resolver="/etc/resolv.conf"):
|
||||
nameservers = []
|
||||
domain = None
|
||||
searches = None
|
||||
|
||||
try:
|
||||
with open(resolver, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("search "):
|
||||
searches = ' '.join(line.split(' ')[1:])
|
||||
if line.startswith("domain "):
|
||||
domain = line.split(' ')[1]
|
||||
if line.startswith("nameserver "):
|
||||
nameservers.append(line.split(' ')[1])
|
||||
except EnvironmentError:
|
||||
log.warn("Unable to open /etc/resolv.conf")
|
||||
|
||||
# Always remove admin interface IP from nameserver list
|
||||
admin_ip = self.netsettings[self.parent.managediface]["addr"]
|
||||
if admin_ip in nameservers:
|
||||
try:
|
||||
nameservers.remove(admin_ip)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return searches, domain, ",".join(nameservers)
|
||||
|
||||
def save(self, responses):
|
||||
## Generic settings start ##
|
||||
newsettings = dict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
## Generic settings end ##
|
||||
|
||||
#log.debug(str(newsettings))
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
#Set oldsettings to reflect new settings
|
||||
self.oldsettings = newsettings
|
||||
#Update self.defaults
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank":
|
||||
self.defaults[fieldname]['value'] = newsettings[fieldname]
|
||||
|
||||
def checkDNS(self, server):
|
||||
#Note: Python's internal resolver caches negative answers.
|
||||
#Therefore, we should call dig externally to be sure.
|
||||
|
||||
noout = open('/dev/null', 'w')
|
||||
dns_works = subprocess.call(["dig", "+short", "+time=3",
|
||||
"+retries=1",
|
||||
self.defaults["TEST_DNS"]['value'],
|
||||
"@%s" % server], stdout=noout,
|
||||
stderr=noout)
|
||||
if dns_works != 0:
|
||||
return False
|
||||
else:
|
||||
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 radioSelect(self, current, state, user_data=None):
|
||||
pass
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,169 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2014 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.
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except Exception:
|
||||
# python 2.6 or earlier use backport
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
|
||||
log = logging.getLogger('fuelmenu.rootpw')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class fueluser(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "Fuel User"
|
||||
self.priority = 1
|
||||
self.visible = True
|
||||
self.parent = parent
|
||||
# UI text
|
||||
self.header_content = [
|
||||
"Set Fuel User password.",
|
||||
"Default user: admin",
|
||||
"Default password: admin",
|
||||
"",
|
||||
"The password should contain upper and lower-case letters, digits"
|
||||
", and characters like !@#$%^&*()_+."
|
||||
]
|
||||
self.fields = ["FUEL_ACCESS/password", "CONFIRM_PASSWORD"]
|
||||
self.defaults = \
|
||||
{
|
||||
"FUEL_ACCESS/password": {"label": "Fuel password",
|
||||
"tooltip": "ASCII characters only",
|
||||
"value": ""},
|
||||
"CONFIRM_PASSWORD": {"label": "Confirm password",
|
||||
"tooltip": "ASCII characters only",
|
||||
"value": ""},
|
||||
}
|
||||
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
|
||||
def check(self, args):
|
||||
"""Validate that all fields have valid values and sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
# Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank":
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
|
||||
# Validate each field
|
||||
errors = []
|
||||
|
||||
# Passwords must match
|
||||
if responses["FUEL_ACCESS/password"] != responses["CONFIRM_PASSWORD"]:
|
||||
# Ignore if password is unchanged
|
||||
if responses["FUEL_ACCESS/password"] != self.defaults[
|
||||
'FUEL_ACCESS/password']['value']:
|
||||
errors.append("Passwords do not match.")
|
||||
|
||||
# Password must not be empty
|
||||
if len(responses["FUEL_ACCESS/password"]) == 0:
|
||||
errors.append("Password must not be empty.")
|
||||
|
||||
# Password needs to be in ASCII character set
|
||||
try:
|
||||
if responses["FUEL_ACCESS/password"].decode('ascii'):
|
||||
pass
|
||||
except UnicodeDecodeError:
|
||||
errors.append("Password contains non-ASCII characters.")
|
||||
|
||||
if len(errors) > 0:
|
||||
self.parent.footer.set_text("Error: %s" % (errors[0]))
|
||||
log.error("Errors: %s %s" % (len(errors), errors))
|
||||
return False
|
||||
else:
|
||||
self.parent.footer.set_text("No errors found.")
|
||||
# Remove confirm from responses so it isn't saved
|
||||
del responses["CONFIRM_PASSWORD"]
|
||||
return responses
|
||||
|
||||
def apply(self, args):
|
||||
responses = self.check(args)
|
||||
if responses is False:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "FUEL_ACCESS/password":
|
||||
return (self.edits[index].get_edit_text() == "")
|
||||
return False
|
||||
self.save(responses)
|
||||
return True
|
||||
|
||||
def save(self, responses):
|
||||
## Generic settings start ##
|
||||
newsettings = OrderedDict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
try:
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
except Exception:
|
||||
if part1 not in newsettings.keys():
|
||||
newsettings[part1] = OrderedDict()
|
||||
log.warning("issues setting newsettings %s " % setting)
|
||||
log.warning("current newsettings: %s" % newsettings)
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
self.parent.footer.set_text("Changes applied successfully.")
|
||||
# Reset fields
|
||||
self.cancel(None)
|
||||
|
||||
def load(self):
|
||||
# Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
|
||||
oldsettings = Settings().read(self.parent.settingsfile)
|
||||
for setting in self.defaults.keys():
|
||||
try:
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
except Exception:
|
||||
log.warning("No setting named %s found." % setting)
|
||||
continue
|
||||
return oldsettings
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,435 +0,0 @@
|
|||
#!/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)
|
|
@ -1,266 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
from fuelmenu.common import dialog
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
import fuelmenu.common.urwidwrapper as widget
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.mirrors')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class ntpsetup(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "Time Sync"
|
||||
self.priority = 60
|
||||
self.visible = True
|
||||
self.deployment = "pre"
|
||||
self.parent = parent
|
||||
|
||||
#UI details
|
||||
self.header_content = ["NTP Setup", "Note: If you continue without "
|
||||
"NTP, you may have issues with deployment "
|
||||
"due to time synchronization issues. These "
|
||||
"problems are exacerbated in virtualized "
|
||||
"environments."]
|
||||
|
||||
self.fields = ["ntpenabled", "NTP1", "NTP2", "NTP3"]
|
||||
self.defaults = \
|
||||
{
|
||||
"ntpenabled": {"label": "Enable NTP:",
|
||||
"tooltip": "",
|
||||
"value": "radio"},
|
||||
"NTP1": {"label": "NTP Server 1:",
|
||||
"tooltip": "NTP Server for time synchronization",
|
||||
"value": "time.nist.gov"},
|
||||
"NTP2": {"label": "NTP Server 2:",
|
||||
"tooltip": "NTP Server for time synchronization",
|
||||
"value": "time-a.nist.gov"},
|
||||
"NTP3": {"label": "NTP Server 3:",
|
||||
"tooltip": "NTP Server for time synchronization",
|
||||
"value": "time-b.nist.gov"},
|
||||
}
|
||||
|
||||
#Load info
|
||||
self.gateway = self.get_default_gateway_linux()
|
||||
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
|
||||
def check(self, args):
|
||||
"""Validate that all fields have valid values and sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
#Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "blank":
|
||||
pass
|
||||
elif fieldname == "ntpenabled":
|
||||
rb_group = self.edits[index].rb_group
|
||||
if rb_group[0].state:
|
||||
responses["ntpenabled"] = "Yes"
|
||||
else:
|
||||
responses["ntpenabled"] = "No"
|
||||
else:
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
|
||||
###Validate each field
|
||||
errors = []
|
||||
warnings = []
|
||||
if responses['ntpenabled'] == "No":
|
||||
#Disabled NTP means passing no NTP servers to save method
|
||||
responses = {
|
||||
'NTP1': "",
|
||||
'NTP2': "",
|
||||
'NTP3': ""}
|
||||
self.parent.footer.set_text("No errors found.")
|
||||
log.info("No errors found")
|
||||
return responses
|
||||
if all(map(lambda f: (len(responses[f]) == 0), self.fields)):
|
||||
pass
|
||||
#We will allow empty if user doesn't need external networking
|
||||
#and present a strongly worded warning
|
||||
#msg = "If you continue without NTP, you may have issues with "\
|
||||
# + "deployment due to time synchronization issues. These "\
|
||||
# + "problems are exacerbated in virtualized deployments."
|
||||
|
||||
#dialog.display_dialog(
|
||||
# self, widget.TextLabel(msg), "Empty NTP Warning")
|
||||
del responses['ntpenabled']
|
||||
for ntpfield, ntpvalue in responses.iteritems():
|
||||
#NTP must be under 255 chars
|
||||
if len(ntpvalue) >= 255:
|
||||
errors.append("%s must be under 255 chars." %
|
||||
self.defaults[ntpfield]['label'])
|
||||
|
||||
#NTP needs to have valid chars
|
||||
if re.search('[^a-zA-Z0-9-.]', ntpvalue):
|
||||
errors.append("%s contains illegal characters." %
|
||||
self.defaults[ntpfield]['label'])
|
||||
|
||||
#ensure external NTP is valid
|
||||
if len(ntpvalue) > 0:
|
||||
#Validate first NTP address
|
||||
try:
|
||||
#Try to test NTP via ntpdate
|
||||
if not self.checkNTP(ntpvalue):
|
||||
warnings.append("%s unable to perform NTP."
|
||||
% self.defaults[ntpfield]['label'])
|
||||
except Exception:
|
||||
warnings.append("%s unable to sync time with server.: %s"
|
||||
% self.defaults[ntpfield]['label'])
|
||||
if len(errors) > 0:
|
||||
self.parent.footer.set_text(
|
||||
"Errors: %s First error: %s" % (len(errors), errors[0]))
|
||||
log.error("Errors: %s %s" % (len(errors), errors))
|
||||
return False
|
||||
else:
|
||||
if len(warnings) > 0:
|
||||
msg = ["NTP configuration has the following warnings:"]
|
||||
msg.extend(warnings)
|
||||
msg.append("You may see errors during provisioning and "
|
||||
"in system logs. NTP errors are not fatal.")
|
||||
warning_msg = '\n'.join(str(line) for line in msg)
|
||||
dialog.display_dialog(self, widget.TextLabel(warning_msg),
|
||||
"NTP Warnings")
|
||||
log.warning(warning_msg)
|
||||
self.parent.footer.set_text("No errors found.")
|
||||
log.info("No errors found")
|
||||
return responses
|
||||
|
||||
def apply(self, args):
|
||||
responses = self.check(args)
|
||||
if responses is False:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
return False
|
||||
|
||||
self.save(responses)
|
||||
#Apply NTP now
|
||||
if len(responses['NTP1']) > 0:
|
||||
#Stop ntpd, run ntpdate, start ntpd
|
||||
noout = open('/dev/null', 'w')
|
||||
subprocess.call(["service", "ntpd", "stop"],
|
||||
stdout=noout, stderr=noout)
|
||||
subprocess.call(["ntpdate", "-t5", responses['NTP1']],
|
||||
stdout=noout, stderr=noout)
|
||||
subprocess.call(["service", "ntpd", "start"],
|
||||
stdout=noout, stderr=noout)
|
||||
return True
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def get_default_gateway_linux(self):
|
||||
return ModuleHelper.get_default_gateway_linux()
|
||||
|
||||
def load(self):
|
||||
#Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
|
||||
oldsettings = Settings().read(self.parent.settingsfile)
|
||||
for setting in self.defaults.keys():
|
||||
try:
|
||||
if setting == "ntpenabled":
|
||||
rb_group = \
|
||||
self.edits[self.fields.index("ntpenabled")].rb_group
|
||||
if oldsettings[setting]["value"] == "Yes":
|
||||
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 "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
except Exception:
|
||||
log.warning("No setting named %s found." % setting)
|
||||
continue
|
||||
return oldsettings
|
||||
|
||||
def save(self, responses):
|
||||
## Generic settings start ##
|
||||
newsettings = dict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
## Generic settings end ##
|
||||
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
#Set oldsettings to reflect new settings
|
||||
self.oldsettings = newsettings
|
||||
#Update defaults
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank" and fieldname in newsettings:
|
||||
self.defaults[fieldname]['value'] = newsettings[fieldname]
|
||||
|
||||
def checkNTP(self, server):
|
||||
# Use ntpdate to verify server answers NTP requests
|
||||
|
||||
noout = open('/dev/null', 'w')
|
||||
ntp_works = subprocess.call(["ntpdate", "-q", "-t2", server],
|
||||
stdout=noout, stderr=noout)
|
||||
return (ntp_works == 0)
|
||||
|
||||
def refresh(self):
|
||||
self.gateway = self.get_default_gateway_linux()
|
||||
log.info("refresh. gateway is %s" % self.gateway)
|
||||
#If gateway is empty, disable NTP
|
||||
if self.gateway is None:
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "ntpenabled":
|
||||
log.info("clearing ntp enabled")
|
||||
log.info("fieldname: %s" % fieldname)
|
||||
rb_group = self.edits[index].rb_group
|
||||
rb_group[0].set_state(False)
|
||||
rb_group[1].set_state(True)
|
||||
|
||||
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 screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,131 +0,0 @@
|
|||
#!/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 crypt
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
import logging
|
||||
import subprocess
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
|
||||
log = logging.getLogger('fuelmenu.rootpw')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class rootpw(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "Root Password"
|
||||
self.priority = 60
|
||||
self.visible = True
|
||||
self.parent = parent
|
||||
#UI text
|
||||
self.header_content = ["Set root user password", ""]
|
||||
self.fields = ["PASSWORD", "CONFIRM_PASSWORD"]
|
||||
self.defaults = \
|
||||
{
|
||||
"PASSWORD": {"label": "Enter password",
|
||||
"tooltip": "Use ASCII characters only",
|
||||
"value": ""},
|
||||
"CONFIRM_PASSWORD": {"label": "Confirm password",
|
||||
"tooltip": "Use ASCII characters only",
|
||||
"value": ""},
|
||||
}
|
||||
|
||||
self.screen = None
|
||||
|
||||
def check(self, args):
|
||||
"""Validate that all fields have valid values and sanity checks."""
|
||||
self.parent.footer.set_text("Checking data...")
|
||||
self.parent.refreshScreen()
|
||||
#Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank":
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
|
||||
###Validate each field
|
||||
errors = []
|
||||
|
||||
#Passwords must match
|
||||
if responses["PASSWORD"] != responses["CONFIRM_PASSWORD"]:
|
||||
errors.append("Passwords do not match.")
|
||||
|
||||
#password must not be empty
|
||||
if len(responses["PASSWORD"]) == 0:
|
||||
errors.append("Password must not be empty.")
|
||||
|
||||
#password needs to be in ASCII character set
|
||||
try:
|
||||
if responses["PASSWORD"].decode('ascii'):
|
||||
pass
|
||||
except UnicodeDecodeError:
|
||||
errors.append("Password contains non-ASCII characters.")
|
||||
|
||||
if len(errors) > 0:
|
||||
self.parent.footer.set_text("Error: %s" % (errors[0]))
|
||||
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:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "PASSWORD":
|
||||
return (self.edits[index].get_edit_text() == "")
|
||||
return False
|
||||
|
||||
hashed = crypt.crypt(responses["PASSWORD"])
|
||||
log.info("Changing root password")
|
||||
try:
|
||||
#clear any locks first
|
||||
noout = open('/dev/null', 'w')
|
||||
subprocess.call(["rm", "-f", "/etc/passwd.lock",
|
||||
"/etc/shadow.lock"], stdout=noout,
|
||||
stderr=noout)
|
||||
retcode = subprocess.call(["usermod", "-p", hashed, "root"],
|
||||
stdout=noout,
|
||||
stderr=noout)
|
||||
except OSError:
|
||||
log.error("Unable to change password.")
|
||||
self.parent.footer.set_text("Unable to change password.")
|
||||
return False
|
||||
|
||||
if retcode == 0:
|
||||
self.parent.footer.set_text("Changed applied successfully.")
|
||||
log.info("Root password successfully changed.")
|
||||
#Reset fields
|
||||
self.cancel(None)
|
||||
else:
|
||||
self.parent.footer.set_text("Unable to apply changes. Check logs "
|
||||
"for more details.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,77 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
from fuelmenu.common import dialog
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
import fuelmenu.common.urwidwrapper as widget
|
||||
import time
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class saveandquit():
|
||||
def __init__(self, parent):
|
||||
self.name = "Quit Setup"
|
||||
self.priority = 99
|
||||
self.visible = True
|
||||
self.parent = parent
|
||||
self.screen = None
|
||||
#UI text
|
||||
saveandcontinue_button = widget.Button("Save and Continue",
|
||||
self.save_and_continue)
|
||||
saveandquit_button = widget.Button("Save and Quit", self.save_and_quit)
|
||||
quitwithoutsaving_button = widget.Button("Quit without saving",
|
||||
self.quit_without_saving)
|
||||
self.header_content = ["Save configuration before quitting?", blank,
|
||||
saveandcontinue_button, saveandquit_button,
|
||||
quitwithoutsaving_button]
|
||||
|
||||
self.fields = []
|
||||
self.defaults = dict()
|
||||
|
||||
def save_and_continue(self, args):
|
||||
self.save()
|
||||
|
||||
def save_and_quit(self, args):
|
||||
if self.save():
|
||||
self.parent.refreshScreen()
|
||||
time.sleep(1.5)
|
||||
self.parent.exit_program(None)
|
||||
|
||||
def save(self):
|
||||
results, modulename = self.parent.global_save()
|
||||
if results:
|
||||
self.parent.footer.set_text("All changes saved successfully!")
|
||||
return True
|
||||
else:
|
||||
#show pop up with more details
|
||||
msg = "ERROR: Module %s failed to save. Go back" % (modulename)\
|
||||
+ " and fix any mistakes or choose Quit without Saving."
|
||||
dialog.display_dialog(self, widget.TextLabel(msg),
|
||||
"Error saving changes!")
|
||||
return False
|
||||
|
||||
def quit_without_saving(self, args):
|
||||
self.parent.exit_program(None)
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults, buttons_visible=False)
|
|
@ -1,205 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except Exception:
|
||||
# python 2.6 or earlier use backport
|
||||
from ordereddict import OrderedDict
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
from fuelmenu.common import pwgen
|
||||
from fuelmenu.settings import Settings
|
||||
import logging
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
log = logging.getLogger('fuelmenu.servicepws')
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class servicepws(urwid.WidgetWrap):
|
||||
def __init__(self, parent):
|
||||
self.name = "Service Passwords"
|
||||
self.priority = 99
|
||||
self.visible = False
|
||||
self.parent = parent
|
||||
#UI text
|
||||
self.header_content = ["Set service passwords", ""]
|
||||
self.defaults = \
|
||||
{
|
||||
"astute/user": {"label": "Astute user",
|
||||
"tooltip": "",
|
||||
"value": "naily"},
|
||||
"astute/password": {"label": "Astute password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"cobbler/user": {"label": "Cobbler user",
|
||||
"tooltip": "",
|
||||
"value": "cobbler"},
|
||||
"cobbler/password": {"label": "Cobbler password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"keystone/admin_token": {"label": "Keystone Admin Token",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"keystone/nailgun_user": {
|
||||
"label": "Keystone username for Nailgun",
|
||||
"tooltip": "",
|
||||
"value": "nailgun"},
|
||||
"keystone/nailgun_password": {
|
||||
"label": "Keystone password for Nailgun",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"keystone/ostf_user": {
|
||||
"label": "Keystone username for OSTF",
|
||||
"tooltip": "",
|
||||
"value": "ostf"},
|
||||
"keystone/ostf_password": {
|
||||
"label": "Keystone password for OSTF",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"keystone/monitord_user": {
|
||||
"label": "Master node monitoring user",
|
||||
"tooltip": "",
|
||||
"value": "monitord"
|
||||
},
|
||||
"keystone/monitord_password": {
|
||||
"label": "Master node monitoring password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password(),
|
||||
},
|
||||
"mcollective/user": {"label": "Mcollective user",
|
||||
"tooltip": "",
|
||||
"value": "mcollective"},
|
||||
"mcollective/password": {"label": "Mcollective password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"postgres/keystone_dbname": {"label": "Keystone DB name",
|
||||
"tooltip": "",
|
||||
"value": "keystone"},
|
||||
"postgres/keystone_user": {"label": "Keystone DB user",
|
||||
"tooltip": "",
|
||||
"value": "keystone"},
|
||||
"postgres/keystone_password": {"label": "Keystone DB password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"postgres/nailgun_dbname": {"label": "Nailgun DB name",
|
||||
"tooltip": "",
|
||||
"value": "nailgun"},
|
||||
"postgres/nailgun_user": {"label": "Nailgun DB user",
|
||||
"tooltip": "",
|
||||
"value": "nailgun"},
|
||||
"postgres/nailgun_password": {"label": "Nailgun DB password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
"postgres/ostf_dbname": {"label": "OSTF DB name",
|
||||
"tooltip": "",
|
||||
"value": "ostf"},
|
||||
"postgres/ostf_user": {"label": "OSTF DB user",
|
||||
"tooltip": "",
|
||||
"value": "ostf"},
|
||||
"postgres/ostf_password": {"label": "OSTF DB password",
|
||||
"tooltip": "",
|
||||
"value": pwgen.password()},
|
||||
}
|
||||
self.fields = self.defaults.keys()
|
||||
|
||||
self.oldsettings = self.load()
|
||||
self.screen = None
|
||||
|
||||
def check(self, args):
|
||||
#Get field information
|
||||
responses = dict()
|
||||
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "blank":
|
||||
pass
|
||||
else:
|
||||
responses[fieldname] = self.edits[index].get_edit_text()
|
||||
return responses
|
||||
|
||||
def apply(self, args):
|
||||
log.debug('start saving servicepws')
|
||||
responses = self.check(args)
|
||||
if responses is False:
|
||||
log.error("Check failed. Not applying")
|
||||
log.error("%s" % (responses))
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname == "PASSWORD":
|
||||
return (self.edits[index].get_edit_text() == "")
|
||||
return False
|
||||
|
||||
self.save(responses)
|
||||
|
||||
def load(self):
|
||||
#Read in yaml
|
||||
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
|
||||
oldsettings = defaultsettings
|
||||
oldsettings.update(Settings().read(self.parent.settingsfile))
|
||||
|
||||
oldsettings = Settings().read(self.parent.settingsfile)
|
||||
for setting in self.defaults.keys():
|
||||
try:
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
self.defaults[setting]["value"] = oldsettings[part1][part2]
|
||||
else:
|
||||
self.defaults[setting]["value"] = oldsettings[setting]
|
||||
except Exception:
|
||||
log.warning("No setting named %s found." % setting)
|
||||
continue
|
||||
return oldsettings
|
||||
|
||||
def save(self, responses):
|
||||
## Generic settings start ##
|
||||
newsettings = OrderedDict()
|
||||
for setting in responses.keys():
|
||||
if "/" in setting:
|
||||
part1, part2 = setting.split("/")
|
||||
if part1 not in newsettings:
|
||||
#We may not touch all settings, so copy oldsettings first
|
||||
try:
|
||||
newsettings[part1] = self.oldsettings[part1]
|
||||
except Exception:
|
||||
if part1 not in newsettings.keys():
|
||||
newsettings[part1] = OrderedDict()
|
||||
log.warning("issues setting newsettings %s " % setting)
|
||||
log.warning("current newsettings: %s" % newsettings)
|
||||
newsettings[part1][part2] = responses[setting]
|
||||
else:
|
||||
newsettings[setting] = responses[setting]
|
||||
Settings().write(newsettings,
|
||||
defaultsfile=self.parent.defaultsettingsfile,
|
||||
outfn=self.parent.settingsfile)
|
||||
|
||||
## Generic settings end ##
|
||||
log.debug('done saving servicepws')
|
||||
|
||||
#Set oldsettings to reflect new settings
|
||||
self.oldsettings = newsettings
|
||||
#Update defaults
|
||||
for index, fieldname in enumerate(self.fields):
|
||||
if fieldname != "blank" and fieldname in newsettings:
|
||||
self.defaults[fieldname]['value'] = newsettings[fieldname]
|
||||
|
||||
def cancel(self, button):
|
||||
ModuleHelper.cancel(self, button)
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults)
|
|
@ -1,58 +0,0 @@
|
|||
#!/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.
|
||||
|
||||
from fuelmenu.common.modulehelper import ModuleHelper
|
||||
import fuelmenu.common.urwidwrapper as widget
|
||||
import subprocess
|
||||
import urwid
|
||||
import urwid.raw_display
|
||||
import urwid.web_display
|
||||
|
||||
blank = urwid.Divider()
|
||||
|
||||
|
||||
class shell():
|
||||
def __init__(self, parent):
|
||||
self.name = "Shell Login"
|
||||
self.priority = 90
|
||||
self.visible = True
|
||||
self.parent = parent
|
||||
self.screen = None
|
||||
#UI text
|
||||
text1 = "Press the button below to enter a shell login."
|
||||
login_button = widget.Button("Shell Login", self.start_shell)
|
||||
self.header_content = [text1, blank, login_button]
|
||||
self.fields = []
|
||||
self.defaults = dict()
|
||||
|
||||
def check(self, args):
|
||||
return True
|
||||
|
||||
def start_shell(self, args):
|
||||
self.parent.mainloop.screen.stop()
|
||||
message = "Type exit to return to the main UI."
|
||||
|
||||
subprocess.call(
|
||||
"screen bash -c 'clear; echo %s ; echo; bash -i'" % message,
|
||||
shell=True
|
||||
)
|
||||
self.parent.mainloop.screen.start()
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def screenUI(self):
|
||||
return ModuleHelper.screenUI(self, self.header_content, self.fields,
|
||||
self.defaults, buttons_visible=False)
|
|
@ -1,98 +0,0 @@
|
|||
# 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 collections
|
||||
import logging
|
||||
|
||||
from ordereddict import OrderedDict
|
||||
import yaml
|
||||
|
||||
|
||||
def construct_ordered_mapping(self, node, deep=False):
|
||||
if not isinstance(node, yaml.MappingNode):
|
||||
raise yaml.ConstructorError(None, None,
|
||||
"expected a mapping node, but found %s" %
|
||||
node.id, node.start_mark)
|
||||
mapping = OrderedDict()
|
||||
for key_node, value_node in node.value:
|
||||
key = self.construct_object(key_node, deep=deep)
|
||||
if not isinstance(key, collections.Hashable):
|
||||
raise yaml.ConstructorError(
|
||||
"while constructing a mapping", node.start_mark,
|
||||
"found unhashable key", key_node.start_mark)
|
||||
value = self.construct_object(value_node, deep=deep)
|
||||
mapping[key] = value
|
||||
return mapping
|
||||
yaml.constructor.BaseConstructor.construct_mapping = construct_ordered_mapping
|
||||
|
||||
|
||||
def construct_yaml_map_with_ordered_dict(self, node):
|
||||
data = OrderedDict()
|
||||
yield data
|
||||
value = self.construct_mapping(node)
|
||||
data.update(value)
|
||||
yaml.constructor.Constructor.add_constructor(
|
||||
'tag:yaml.org,2002:map',
|
||||
construct_yaml_map_with_ordered_dict)
|
||||
|
||||
|
||||
def represent_ordered_mapping(self, tag, mapping, flow_style=None):
|
||||
value = []
|
||||
node = yaml.MappingNode(tag, value, flow_style=flow_style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
best_style = True
|
||||
if hasattr(mapping, 'items'):
|
||||
mapping = list(mapping.items())
|
||||
for item_key, item_value in mapping:
|
||||
node_key = self.represent_data(item_key)
|
||||
node_value = self.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
|
||||
best_style = False
|
||||
if not (isinstance(node_value, yaml.ScalarNode)
|
||||
and not node_value.style):
|
||||
best_style = False
|
||||
value.append((node_key, node_value))
|
||||
if flow_style is None:
|
||||
if self.default_flow_style is not None:
|
||||
node.flow_style = self.default_flow_style
|
||||
else:
|
||||
node.flow_style = best_style
|
||||
return node
|
||||
yaml.representer.BaseRepresenter.represent_mapping = represent_ordered_mapping
|
||||
yaml.representer.Representer.add_representer(OrderedDict, yaml.representer.
|
||||
SafeRepresenter.represent_dict)
|
||||
|
||||
|
||||
class Settings(object):
|
||||
|
||||
def read(self, yamlfile):
|
||||
try:
|
||||
infile = file(yamlfile, 'r')
|
||||
settings = yaml.load(infile)
|
||||
return settings or OrderedDict()
|
||||
except Exception:
|
||||
if yamlfile is not None:
|
||||
logging.error("Unable to read YAML: %s", yamlfile)
|
||||
return OrderedDict()
|
||||
|
||||
def write(self, newvalues, tree=None, defaultsfile='settings.yaml',
|
||||
outfn='mysettings.yaml'):
|
||||
settings = self.read(defaultsfile)
|
||||
settings.update(self.read(outfn))
|
||||
settings.update(newvalues)
|
||||
outfile = file(outfn, 'w')
|
||||
yaml.dump(settings, outfile, default_style='"',
|
||||
default_flow_style=False)
|
||||
return True
|
|
@ -1,25 +0,0 @@
|
|||
HOSTNAME: "fuel"
|
||||
DNS_DOMAIN: "domain.tld"
|
||||
DNS_SEARCH: "domain.tld"
|
||||
DNS_UPSTREAM: "8.8.8.8"
|
||||
NTP1: "0.fuel.pool.ntp.org"
|
||||
NTP2: "1.fuel.pool.ntp.org"
|
||||
NTP3: "2.fuel.pool.ntp.org"
|
||||
ADMIN_NETWORK:
|
||||
interface: "eth0"
|
||||
ipaddress: "10.20.0.2"
|
||||
netmask: "255.255.255.0"
|
||||
cidr: "10.20.0.0/24"
|
||||
size: "256"
|
||||
dhcp_pool_start: "10.20.0.3"
|
||||
dhcp_pool_end: "10.20.0.254"
|
||||
FUEL_ACCESS:
|
||||
user: "admin"
|
||||
password: "admin"
|
||||
BOOTSTRAP:
|
||||
MIRROR_DISTRO: "http://archive.ubuntu.com/ubuntu"
|
||||
MIRROR_MOS: "http://mirror.fuel-infra.org/mos-repos/ubuntu/7.0"
|
||||
HTTP_PROXY: ""
|
||||
EXTRA_APT_REPOS: ""
|
||||
flavor: "centos"
|
||||
PRODUCTION: docker
|
|
@ -1,48 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 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 mock
|
||||
import ordereddict
|
||||
|
||||
from fuelmenu import settings
|
||||
|
||||
|
||||
def test_read_settings(tmpdir):
|
||||
yaml_file = tmpdir.join("yamlfile.yaml")
|
||||
yaml_file.write("""
|
||||
sample:
|
||||
- one
|
||||
- a: b
|
||||
c: d
|
||||
""")
|
||||
data = settings.Settings().read(yaml_file.strpath)
|
||||
assert data == {
|
||||
'sample': [
|
||||
'one',
|
||||
{
|
||||
'a': 'b',
|
||||
'c': 'd',
|
||||
}
|
||||
]
|
||||
}
|
||||
assert isinstance(data, ordereddict.OrderedDict)
|
||||
|
||||
|
||||
@mock.patch('fuelmenu.settings.file', side_effect=Exception('Error'))
|
||||
def test_read_settings_with_error(_):
|
||||
data = settings.Settings().read('some_path')
|
||||
assert data == {}
|
||||
assert isinstance(data, ordereddict.OrderedDict)
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright 2015 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.
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
tox -v
|
|
@ -1,52 +0,0 @@
|
|||
#!/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 setuptools
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
name="fuelmenu",
|
||||
version='8.0.0',
|
||||
description="Console util for pre-configuration of Fuel server",
|
||||
author="Matthew Mosesohn",
|
||||
author_email="mmosesohn@mirantis.com",
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Environment :: Console :: Curses",
|
||||
"Operating System :: POSIX",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Security",
|
||||
"Topic :: Internet",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"Topic :: Internet :: Proxy Servers",
|
||||
"Topic :: Software Development :: Testing"
|
||||
],
|
||||
install_requires=[
|
||||
'netaddr>=0.7.5',
|
||||
'OrderedDict>=1.1',
|
||||
'PyYAML>=3.10',
|
||||
'netifaces>=0.5',
|
||||
'urwid>=1.1.1',
|
||||
],
|
||||
include_package_data=True,
|
||||
packages=setuptools.find_packages(),
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'fuelmenu = fuelmenu.fuelmenu:main',
|
||||
],
|
||||
},
|
||||
)
|
|
@ -1,47 +0,0 @@
|
|||
%define name fuelmenu
|
||||
%{!?version: %define version 8.0.0}
|
||||
%{!?release: %define release 1}
|
||||
|
||||
Name: %{name}
|
||||
Summary: Console utility for pre-configuration of Fuel server
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
License: Apache
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
Vendor: Matthew Mosesohn <mmosesohn@mirantis.com>
|
||||
BuildRequires: python-setuptools
|
||||
Requires: bind-utils
|
||||
Requires: nailgun-net-check
|
||||
Requires: ntp
|
||||
Requires: python-setuptools
|
||||
Requires: python-netaddr
|
||||
Requires: python-netifaces
|
||||
Requires: python-urwid >= 1.1.0
|
||||
Requires: PyYAML
|
||||
Requires: python-ordereddict
|
||||
Requires: screen
|
||||
Requires: python-six
|
||||
|
||||
%description
|
||||
Summary: Console utility for pre-configuration of Fuel server
|
||||
|
||||
%prep
|
||||
%setup -cq -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py build
|
||||
|
||||
%install
|
||||
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
|
||||
install -d -m 755 %{buildroot}/etc/fuel
|
||||
install -m 600 %{_builddir}/%{name}-%{version}/fuelmenu/fuelmenu/settings.yaml %{buildroot}/etc/fuel/astute.yaml
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%files -f %{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
|
||||
%defattr(-,root,root)
|
||||
%config(noreplace) /etc/fuel/astute.yaml
|
|
@ -1,2 +0,0 @@
|
|||
pytest==2.8.0
|
||||
mock==1.3.0
|
|
@ -1,39 +0,0 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
envlist = py26,py27,pep8
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install --allow-external -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
py.test -vv {posargs:fuelmenu/tests}
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
[testenv:pep8]
|
||||
deps = hacking==0.7
|
||||
usedevelop = False
|
||||
commands =
|
||||
flake8 {posargs:.}
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs:}
|
||||
|
||||
[testenv:devenv]
|
||||
envdir = devenv
|
||||
usedevelop = True
|
||||
|
||||
[flake8]
|
||||
ignore = H234,H302,H802
|
||||
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
|
||||
show-pep8 = True
|
||||
show-source = True
|
||||
count = True
|
||||
|
||||
[hacking]
|
||||
import_exceptions = testtools.matchers
|
|
@ -432,7 +432,6 @@ function run_extensions_tests {
|
|||
function run_flake8 {
|
||||
local result=0
|
||||
run_flake8_subproject nailgun && \
|
||||
run_flake8_subproject fuelmenu && \
|
||||
run_flake8_subproject network_checker && \
|
||||
run_flake8_subproject fuel_upgrade_system/fuel_upgrade && \
|
||||
run_flake8_subproject fuel_upgrade_system/fuel_package_updates && \
|
||||
|
|
|
@ -118,20 +118,16 @@ mv %{_builddir}/%{name}-%{version}/nailgun/compressed_static %{_builddir}/%{name
|
|||
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py build
|
||||
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py build
|
||||
cd %{_builddir}/%{name}-%{version}/shotgun && python setup.py build
|
||||
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py build
|
||||
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py build
|
||||
|
||||
%install
|
||||
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/nailgun/INSTALLED_FILES
|
||||
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
|
||||
cd %{_builddir}/%{name}-%{version}/shotgun && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/shotgun/INSTALLED_FILES
|
||||
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
|
||||
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates/INSTALLED_FILES
|
||||
mkdir -p %{buildroot}/opt/nailgun/bin
|
||||
mkdir -p %{buildroot}/etc/cron.d
|
||||
mkdir -p %{buildroot}/etc/fuel
|
||||
install -d -m 755 %{buildroot}/etc/fuel
|
||||
install -m 600 %{_builddir}/%{name}-%{version}/fuelmenu/fuelmenu/settings.yaml %{buildroot}/etc/fuel/astute.yaml
|
||||
install -m 755 %{_builddir}/%{name}-%{version}/bin/fencing-agent.rb %{buildroot}/opt/nailgun/bin/fencing-agent.rb
|
||||
install -m 644 %{_builddir}/%{name}-%{version}/bin/fencing-agent.cron %{buildroot}/etc/cron.d/fencing-agent
|
||||
install -p -D -m 755 %{_builddir}/%{name}-%{version}/bin/download-debian-installer %{buildroot}%{_bindir}/download-debian-installer
|
||||
|
@ -215,38 +211,6 @@ Shotgun package.
|
|||
%files -n shotgun -f %{_builddir}/%{name}-%{version}/shotgun/INSTALLED_FILES
|
||||
%defattr(-,root,root)
|
||||
|
||||
%package -n fuelmenu
|
||||
|
||||
Summary: Console utility for pre-configuration of Fuel server
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
License: Apache
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
Vendor: Matthew Mosesohn <mmosesohn@mirantis.com>
|
||||
BuildRequires: python-setuptools
|
||||
Requires: bind-utils
|
||||
Requires: nailgun-net-check
|
||||
Requires: ntp
|
||||
Requires: python-setuptools
|
||||
Requires: python-netaddr
|
||||
Requires: python-netifaces
|
||||
Requires: python-urwid >= 1.1.0
|
||||
Requires: PyYAML
|
||||
Requires: python-ordereddict
|
||||
Requires: screen
|
||||
Requires: python-six
|
||||
|
||||
%description -n fuelmenu
|
||||
Summary: Console utility for pre-configuration of Fuel server
|
||||
|
||||
%files -n fuelmenu -f %{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
|
||||
%defattr(-,root,root)
|
||||
%config(noreplace) /etc/fuel/astute.yaml
|
||||
|
||||
|
||||
%package -n fencing-agent
|
||||
Summary: Fencing agent
|
||||
Version: %{version}
|
||||
|
|
Loading…
Reference in New Issue