move fuelmenu to base level

This commit is contained in:
Matthew Mosesohn 2013-09-25 15:33:15 +04:00
parent 6041bf10c9
commit d2401ec3e1
21 changed files with 2596 additions and 0 deletions

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
recursive-include fuelmenu *.py *.yaml *.default
include setup.py
include README
#recursive-include fuelmenu *

25
README Normal file
View File

@ -0,0 +1,25 @@
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.

0
fuelmenu/__init__.py Normal file
View File

View File

61
fuelmenu/common/dialog.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import copy
import socket
import struct
import re
import netaddr
sys.path.append("/home/mmosesohn/git/fuel/iso/fuelmenu")
from fuelmenu.settings import *
from fuelmenu.common.urwidwrapper import *
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
logging.debug("start modal")
logging.debug(type(body))
if type(body) in [str, unicode]:
body = urwid.Text(body)
logging.debug("Set text widget")
self.title = title
bodybox = urwid.LineBox(urwid.Pile([body, blank,
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)
logging.debug(overlay.contents[0])
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

View File

@ -0,0 +1,51 @@
import yaml
import ConfigParser
import collections
import os
try:
from collections import OrderedDict
except:
# python 2.6 or earlier use backport
from ordereddict import OrderedDict
class NailyFacterSettings():
def __init__(self):
self.defaultsfile = "/etc/naily.facts.default"
self.settingsfile = "/etc/naily.facts"
def read(self, infile='/etc/naily.facts.default'):
config = OrderedDict()
if os.path.isfile(infile):
fd = open(infile, 'r')
lines = fd.readlines()
for line in lines:
key = line.split('=')[0]
value = line.split('=')[1]
config[key] = value
fd.close()
return config
def write(self, newvalues, prefix='mnbs_',
defaultsfile=None, outfn=None):
#Read outfn if it exists
if not defaultsfile:
defaultsfile = self.defaultsfile
if not outfn:
outfn = self.settingsfile
config = OrderedDict()
if defaultsfile is not None:
config.update(self.read(defaultsfile))
if os.path.isfile(outfn):
config.update(self.read(outfn))
#Insert newvalues with prefix into config
for key in newvalues.keys():
config["%s%s" % (prefix, key)] = "%s\n" % newvalues[key]
#Write out new file
outfile = open(outfn, 'w')
for key in config.keys():
outfile.write("%s=%s" % (key, config[key]))
outfile.close()
return True

View File

@ -0,0 +1,41 @@
import netaddr
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:
return False
def getCidr(ip, netmask):
try:
ipn = netaddr.IPNetwork("%s/%s" % (ip, netmask))
return str(ipn.cidr)
except:
return False
def getCidrSize(cidr):
try:
ipn = netaddr.IPNetwork(cidr)
return ipn.size
except:
return False
def getNetwork(ip, netmask):
#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]
return ipn_list
except:
return False

62
fuelmenu/common/puppet.py Normal file
View File

@ -0,0 +1,62 @@
import subprocess
import sys
import logging
#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(classname, name=None, params=None):
#name should be a string
#params should be a dict
'''Runs puppet apply -e "classname {'name': params}" '''
log = logging
log.basicConfig(filename='./fuelmenu.log', level=logging.DEBUG)
log.info("Puppet start")
command = ["puppet", "apply", "-d", "-v", "--logdest", "/tmp/puppet.log"]
input = [classname, "{", '"%s":' % name]
#Build params
for key, value in params.items():
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))[0]
except Exception, e:
import traceback
log.error(traceback.print_exc())
log.error(e)
log.debug(output)
log.debug(e)
if "err:" in output:
log.error(e)
return False
else:
log.debug(output)
return True

View File

@ -0,0 +1,11 @@
import re
def replaceInFile(filename, orig, new):
lines = open(filename).readlines()
for lineno, line in enumerate(lines):
lines[lineno] = re.sub(orig, new, line)
f = open(filename, 'w')
f.write("".join(lines))
f.flush()
f.close()

View File

@ -0,0 +1,90 @@
import urwid
import urwid.raw_display
import urwid.web_display
def TextField(keyword, label, width, default_value=None, tooltip=None,
toolbar=None, disabled=False):
"""Returns an Urwid Edit object"""
if not tooltip:
edit_obj = urwid.Edit(('important', label.ljust(width)), default_value)
else:
edit_obj = TextWithTip(('important', label.ljust(width)),
default_value, tooltip, toolbar)
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(self, choices, default_value=None, fn=None):
"""Returns list of RadioButtons and a horizontal Urwid GridFlow with
radio choices on one line."""
rb_group = []
for txt in choices:
#if default_value == None:
# is_default = "first True"
#else:
# is_default = True if txt == default_value else False
is_default = True if txt == default_value else False
radio_button = urwid.AttrWrap(urwid.RadioButton(rb_group, txt,
is_default, on_state_change=fn,
user_data=txt),
'buttn', 'buttnf')
wrapped_choices = urwid.GridFlow(rb_group, 13, 3, 0, 'left')
#Bundle rb_group so we can use it later easily
wrapped_choices.rb_group = rb_group
return wrapped_choices
def TextLabel(text):
"""Returns an Urwid text object"""
return urwid.Text(text)
def HorizontalGroup(objects, cell_width, align="left"):
"""Returns a padded Urwid GridFlow object that is left aligned"""
return urwid.Padding(urwid.GridFlow(objects, cell_width, 1, 0, align),
left=0, right=0, min_width=61)
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(urwid.Columns(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 TextWithTip(urwid.Edit):
def __init__(self, label, default_value=None, tooltip=None, toolbar=None):
urwid.Edit.__init__(self, caption=label, edit_text=default_value)
self.tip = tooltip
self.toolbar = toolbar
#def keypress(self, size, key):
# key = super(TextWithTip, self).keypress(size, key)
# self.toolbar.set_text(self.tip)
# return key
def render(self, size, focus=False):
if focus:
self.toolbar.set_text(self.tip)
canv = super(TextWithTip, self).render(size, focus)
return canv
#def mouse_event(self, size, event, button, x, y, focus):
# self.toolbar.set_text(self.tip)
# (maxcol,) = size
# if button==1:
# return self.move_cursor_to_coords( (maxcol,), x, y )

249
fuelmenu/fuelmenu.py Executable file
View File

@ -0,0 +1,249 @@
import logging
import operator
import os
import subprocess
import sys
import urwid
import urwid.raw_display
import urwid.web_display
# set up logging
#logging.basicConfig(filename='./fuelmenu.log')
#logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(filename='./fuelmenu.log', level=logging.DEBUG)
log = logging.getLogger('fuelmenu.loader')
class Loader:
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 not module_dir 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
#imported = process(module)
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
if modobj.visible:
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)
version = "3.2"
class FuelSetup():
def __init__(self):
self.footer = None
self.frame = None
self.screen = None
self.defaultsettingsfile = "%s/settings.yaml" \
% (os.path.dirname(__file__))
self.settingsfile = "%s/newsettings.yaml" \
% (os.path.dirname(__file__))
self.managediface = "eth0"
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.get_label() == choice:
item.set_attr_map({None: 'header'})
else:
item.set_attr_map({None: None})
except Exception, e:
log.info("%s" % item)
log.error("%s" % e)
self.setChildScreen(name=choice)
def setChildScreen(self, name=None):
if name is None:
child = self.children[0]
else:
child = self.children[int(self.choices.index(name))]
if not child.screen:
child.screen = child.screenUI()
self.childpage = 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)
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 main(self):
#Disable kernel print messages. They make our UI ugly
noout = open('/dev/null', 'w')
retcode = 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."
% version)
text_footer = (u"Status messages go here.")
blank = urwid.Divider()
#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)
self.menuitems = self.menu(u'Menu', self.choices)
menufill = urwid.Filler(self.menuitems, 'top', 40)
self.menubox = urwid.BoxAdapter(menufill, 40)
child = self.children[0]
self.childpage = 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()
self.mainloop = urwid.MainLoop(self.frame, palette, self.screen,
unhandled_input=unhandled)
self.mainloop.run()
def exit_program(self, button):
#return kernel logging to normal
noout = open('/dev/null', 'w')
retcode = subprocess.call(["sysctl", "-w", "kernel.printk=7 4 1 7"],
stdout=noout,
stderr=noout)
raise urwid.ExitMainLoop()
def setup():
urwid.web_display.set_preferences("Fuel Setup")
# try to handle short web requests quickly
if urwid.web_display.handle_short_request():
return
fm = FuelSetup()
if '__main__' == __name__ or urwid.web_display.is_web_request():
if urwid.VERSION < (1, 1, 0):
print "This program requires urwid 1.1.0 or greater."
setup()

View File

@ -0,0 +1,556 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import re
import copy
import socket
import struct
import netaddr
import dhcp_checker.api
import netifaces
import subprocess
from fuelmenu.settings import *
from fuelmenu.common import network, puppet, replace, nailyfactersettings, \
dialog
from fuelmenu.common.urwidwrapper import *
log = logging.getLogger('fuelmenu.pxe_setup')
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
#fields = ["hostname", "domain", "mgmt_if","dhcp_start","dhcp_end",
# "blank","ext_if","ext_dns"]
fields = ["static_label",
"ADMIN_NETWORK/static_start", "ADMIN_NETWORK/static_end",
"blank", "dynamic_label", "ADMIN_NETWORK/first",
"ADMIN_NETWORK/last"]
facter_translate = {
"ADMIN_NETWORK/interface": "internal_interface",
"ADMIN_NETWORK/ipaddr": "internal_ipaddress",
"ADMIN_NETWORK/netmask": "internal_netmask",
"ADMIN_NETWORK/first": "dhcp_pool_start",
"ADMIN_NETWORK/last": "dhcp_pool_end",
"ADMIN_NETWORK/static_start": "static_pool_start",
"ADMIN_NETWORK/static_end": "static_pool_end",
}
mnbs_internal_ipaddress = "10.20.0.2"
mnbs_internal_netmask = "255.255.255.0"
mnbs_static_pool_start = "10.20.0.130"
mnbs_static_pool_end = "10.20.0.250"
mnbs_dhcp_pool_start = "10.20.0.10"
mnbs_dhcp_pool_end = "10.20.0.120"
mnbs_internal_interface = "eth1"
DEFAULTS = {
#"ADMIN_NETWORK/interface" : { "label" : "Management Interface",
# "tooltip": "This is the INTERNAL network for provisioning",
# "value" : "eth0"},
"ADMIN_NETWORK/first": {"label": "DHCP Pool Start",
"tooltip": "Used for defining IPs for hosts and \
instance public addresses",
"value": "10.0.0.130"},
"ADMIN_NETWORK/last": {"label": "DHCP Pool End",
"tooltip": "Used for defining IPs for hosts and \
instance public addresses",
"value": "10.0.0.254"},
"static_label": {"label": "Static pool for installed nodes:",
"tooltip": "",
"value": "label"},
"ADMIN_NETWORK/static_start": {"label": "Static Pool Start",
"tooltip": "Static pool for installed \
nodes",
"value": "10.0.0.10"},
"ADMIN_NETWORK/static_end": {"label": "Static Pool End",
"tooltip": "Static pool for installed nodes",
"value": "10.0.0.120"},
"dynamic_label": {"label": "DHCP pool for node discovery:",
"tooltip": "",
"value": "label"},
#"ADMIN_NETWORK/dynamic_start" : { "label" : "Static Pool Start",
# "tooltip": "DHCP pool for node discovery",
# "value" : "10.0.0.10"},
#"ADMIN_NETWORK/dynamic_end": { "label" : "Static Pool End",
# "tooltip": "DHCP pool for node discovery",
# "value" : "10.0.0.120"},
}
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
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()
#Get field information
responses = dict()
for index, fieldname in enumerate(fields):
if fieldname == "blank" or "label" in fieldname:
pass
else:
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/ipaddr"] = 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'}]
dhcp_server_data = dhcp_checker.api.check_dhcp_on_eth(
self.activeiface, timeout=5)
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(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(TextLabel(""))
for index, dhcp_server in enumerate(dhcp_server_data):
dhcp_info.append(TextLabel("DHCP Server #%s:" % (index+1)))
dhcp_info.append(TextLabel("IP address: %-10s" %
dhcp_server['server_ip']))
dhcp_info.append(TextLabel("MAC address: %-10s" %
dhcp_server['mac']))
dhcp_info.append(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 Static Pool Start and Static Pool are valid IPs
try:
if netaddr.valid_ipv4(responses[
"ADMIN_NETWORK/static_start"]):
static_start = netaddr.IPAddress(responses[
"ADMIN_NETWORK/static_start"])
else:
raise Exception("")
except Exception, e:
errors.append("Not a valid IP address for Static Pool "
"Start: %s" % e)
try:
if netaddr.valid_ipv4(responses[
"ADMIN_NETWORK/static_end"]):
static_end = netaddr.IPAddress(responses[
"ADMIN_NETWORK/static_end"])
else:
raise Exception("")
except:
errors.append("Invalid IP address for Static Pool end: "
"%s" % responses["ADMIN_NETWORK/static_end"])
#Ensure DHCP Pool Start and DHCP Pool are valid IPs
try:
if netaddr.valid_ipv4(responses["ADMIN_NETWORK/first"]):
dhcp_start = netaddr.IPAddress(
responses["ADMIN_NETWORK/first"])
else:
raise Exception("")
except Exception, e:
errors.append("Invalid IP address for DHCP Pool Start:"
" %s" % e)
try:
if netaddr.valid_ipv4(responses["ADMIN_NETWORK/last"]):
dhcp_end = netaddr.IPAddress(
responses["ADMIN_NETWORK/last"])
else:
raise Exception("")
except:
errors.append("Invalid IP address for DHCP Pool end: %s"
% responses["ADMIN_NETWORK/last"])
#Ensure pool start and end are in the same subnet of each other
netmask = self.netsettings[responses["ADMIN_NETWORK/interface"]
]["netmask"]
if network.inSameSubnet(responses["ADMIN_NETWORK/first"],
responses["ADMIN_NETWORK/last"],
netmask) is False:
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/first"],
mgmt_if_ipaddr, netmask) is False:
errors.append("DHCP Pool start does not match management"
" network.")
if network.inSameSubnet(responses["ADMIN_NETWORK/last"],
mgmt_if_ipaddr, netmask) is False:
errors.append("DHCP Pool end is not in the same subnet as"
" management interface.")
if len(errors) > 0:
self.parent.footer.set_text("Errors: %s First error: %s"
% (len(errors), errors[0]))
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)
#Need to decide if we are pre-deployment or post-deployment
if self.deployment == "post":
self.updateCobbler(responses)
def updateCobbler(self, params):
patterns = {
'cblr_server': '^server: .*',
'cblr_next_server': '^next_server: .*',
'mgmt_if': '^interface=.*',
'domain': '^domain=.*',
'server': '^server=.*',
'dhcp-range': '^dhcp-range=',
'dhcp-option': '^dhcp-option=',
'pxe-service': '^pxe-service=(^,)',
'dhcp-boot': '^dhcp-boot=([^,],{3}),'
}
def cancel(self, button):
for index, fieldname in enumerate(fields):
if fieldname == "blank" or "label" in fieldname:
pass
else:
self.edits[index].set_edit_text(DEFAULTS[fieldname]['value'])
self.setNetworkDetails()
def load(self):
#Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
log.debug("Old settings %s" % oldsettings)
for setting in DEFAULTS.keys():
if "label" in setting:
continue
elif "/" in setting:
part1, part2 = setting.split("/")
DEFAULTS[setting]["value"] = oldsettings[part1][part2]
else:
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 = 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 ##
## Need to calculate and set cidr, netmask, size
newsettings['ADMIN_NETWORK']['netmask'] = self.netsettings[newsettings
['ADMIN_NETWORK']['interface']]["netmask"]
newsettings['ADMIN_NETWORK']['cidr'] = network.getCidr(
self.netsettings[newsettings['ADMIN_NETWORK']['interface']][
"addr"], newsettings['ADMIN_NETWORK']['netmask'])
newsettings['ADMIN_NETWORK']['size'] = network.getCidrSize(
newsettings['ADMIN_NETWORK']['cidr'])
log.debug(str(newsettings))
Settings().write(newsettings, defaultsfile=self.parent.settingsfile,
outfn="newsettings.yaml")
#Write naily.facts
factsettings = dict()
#for key in newsettings.keys():
log.debug(str(facter_translate))
log.debug(str(newsettings))
for key in facter_translate.keys():
factsettings[facter_translate[key]] = responses[key]
n = nailyfactersettings.NailyFacterSettings()
log.debug("Facts to write: %s" % factsettings)
n.write(factsettings)
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update DEFAULTS
for index, fieldname in enumerate(fields):
if fieldname != "blank" and "label" not in fieldname:
DEFAULTS[fieldname]['value'] = responses[fieldname]
self.parent.footer.set_text("Changes saved successfully.")
def getNetwork(self):
"""Uses netifaces module to get addr, broadcast, netmask about
network interfaces"""
for iface in netifaces.interfaces():
if 'lo' in iface or 'vir' in iface:
#if 'lo' in iface or 'vir' in iface or 'vbox' in iface:
if iface != "virbr2-nic":
continue
try:
self.netsettings.update({iface: netifaces.ifaddresses(iface)[
netifaces.AF_INET][0]})
self.netsettings[iface]["onboot"] = "Yes"
except:
self.netsettings.update({iface: {"addr": "", "netmask": "",
"onboot": "no"}})
self.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()
self.netsettings[iface]["link"] = content[0].strip()
except:
self.netsettings[iface]["link"] = "unknown"
#Change unknown link state to up if interface has an IP
if self.netsettings[iface]["link"] == "unknown":
if self.netsettings[iface]["addr"] != "":
self.netsettings[iface]["link"] = "up"
#Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV
self.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):
self.netsettings[iface]['bootproto'] = \
line.split('=').strip()
break
except:
#Check for dhclient process running for this interface
if self.getDHCP(iface):
self.netsettings[iface]['bootproto'] = "dhcp"
else:
self.netsettings[iface]['bootproto'] = "none"
self.gateway = self.get_default_gateway_linux()
def getDHCP(self, 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)
if dhclient_running == 0:
return True
else:
return False
def get_default_gateway_linux(self):
"""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)))
def radioSelectIface(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.")
#Calculate and set Static/DHCP pool fields
#Max IPs = net size - 2 (master node + bcast)
net_ip_list = network.getNetwork(
self.netsettings[self.activeiface]['addr'],
self.netsettings[self.activeiface]['netmask'])
try:
half = int(len(net_ip_list)/2)
static_pool = list(net_ip_list[:half])
dhcp_pool = list(net_ip_list[half:])
static_start = str(static_pool[0])
static_end = str(static_pool[-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:
#We don't have valid values, so mark all fields empty
static_start = ""
static_end = ""
dynamic_start = ""
dynamic_end = ""
for index, key in enumerate(fields):
if key == "ADMIN_NETWORK/static_start":
self.edits[index].set_edit_text(static_start)
elif key == "ADMIN_NETWORK/static_end":
self.edits[index].set_edit_text(static_end)
elif key == "ADMIN_NETWORK/first":
self.edits[index].set_edit_text(dynamic_start)
elif key == "ADMIN_NETWORK/last":
self.edits[index].set_edit_text(dynamic_end)
def refresh(self):
self.getNetwork()
self.setNetworkDetails()
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = urwid.Text("Settings for PXE booting of slave nodes.")
text2 = urwid.Text("Select the interface where PXE will run:")
#Current network settings
self.net_text1 = TextLabel("")
self.net_text2 = TextLabel("")
self.net_text3 = TextLabel("")
self.net_text4 = TextLabel("")
log.debug("Default iface %s" % self.activeiface)
self.net_choices = ChoicesGroup(self, sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelectIface)
self.edits = []
toolbar = self.parent.footer
for key in fields:
#Example: key = hostname, label = Hostname, value = fuel-pm
if key == "blank":
self.edits.append(blank)
elif DEFAULTS[key]["value"] == "radio":
label = TextLabel(DEFAULTS[key]["label"])
choices = ChoicesGroup(self, ["Yes", "No"],
default_value="Yes",
fn=self.radioSelectIface)
self.edits.append(Columns([label, choices]))
elif DEFAULTS[key]["value"] == "label":
self.edits.append(TextLabel(DEFAULTS[key]["label"]))
else:
caption = DEFAULTS[key]["label"]
default = DEFAULTS[key]["value"]
tooltip = DEFAULTS[key]["tooltip"]
self.edits.append(TextField(key, caption, 23, default, tooltip,
toolbar))
#Button to check
button_check = Button("Check", self.check)
#Button to revert to previously saved settings
button_cancel = Button("Cancel", self.cancel)
#Button to apply (and check again)
button_apply = Button("Apply", self.apply)
#Wrap buttons into Columns so it doesn't expand and look ugly
check_col = Columns([button_check, button_cancel,
button_apply, ('weight', 2, blank)])
self.listbox_content = [text1, blank, text2]
self.listbox_content.extend([self.net_choices, self.net_text1,
self.net_text2, self.net_text3,
self.net_text4, blank])
self.listbox_content.extend(self.edits)
self.listbox_content.append(blank)
self.listbox_content.append(check_col)
#Add listeners
#Build all of these into a list
#self.listbox_content = [ text1, blank, blank, edit1, edit2, \
# edit3, edit4, edit5, edit6, button_check ]
self.setNetworkDetails()
#Add everything into a ListBox and return it
self.listwalker = urwid.SimpleListWalker(self.listbox_content)
screen = urwid.ListBox(self.listwalker)
return screen

View File

@ -0,0 +1,470 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import re
import copy
import socket
import struct
import netaddr
import netifaces
import subprocess
from fuelmenu.settings import *
from fuelmenu.common import network, puppet, replace, \
nailyfactersettings, dialog
from fuelmenu.common.urwidwrapper import *
log = logging.getLogger('fuelmenu.mirrors')
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
#fields = ["hostname", "domain", "mgmt_if","dhcp_start","dhcp_end",
# "blank","ext_if","ext_dns"]
fields = ["HOSTNAME", "DNS_DOMAIN", "DNS_SEARCH", "DNS_UPSTREAM", "blank",
"TEST_DNS"]
DEFAULTS = {
"HOSTNAME": {"label": "Hostname",
"tooltip": "Hostname to use for Fuel master node",
"value": socket.gethostname().split('.')[0]},
"DNS_UPSTREAM": {"label": "External DNS",
"tooltip": "DNS server(s) (comma separated) to handle DNS\
requests (example 8.8.8.8)",
"value": "8.8.8.8"},
"DNS_DOMAIN": {"label": "Domain",
"tooltip": "Domain suffix to user for all nodes in your\
cluster",
"value": "domain.tld"},
"DNS_SEARCH": {"label": "Search Domain",
"tooltip": "Domains to search when looking up DNS\
(space separated)",
"value": "domain.tld"},
"TEST_DNS": {"label": "Hostname to test DNS:",
"value": "www.google.com",
"tooltip": "DNS record to resolve to see if DNS is\
accessible"}
}
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
self.oldsettings = self.load()
self.screen = None
self.fixDnsmasqUpstream()
self.fixEtcHosts()
def fixDnsmasqUpstream(self):
#check upstream dns server
with open('/etc/dnsmasq.upstream', 'r') as f:
dnslines = f.readlines()
f.close()
if len(dnslines) > 0:
nameservers = dnslines[0].split(" ")[1:]
for nameserver in nameservers:
if not self.checkDNS(nameserver):
nameservers.remove(nameserver)
else:
nameservers = []
if nameservers == []:
#Write dnsmasq upstream server to default if it's not readable
with open('/etc/dnsmasq.upstream', 'w') as f:
nameservers = DEFAULTS['DNS_UPSTREAM'][
'value'].replace(',', ' ')
f.write("nameserver %s\n" % nameservers)
f.close()
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" % (
managediface_ip, socket.gethostname()))
def check(self, args):
"""Validate that all fields have valid values and some sanity checks"""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
#Get field information
responses = dict()
for index, fieldname in enumerate(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."
diag = dialog.display_dialog(
self, TextLabel(msg), "Empty DNS Warning")
else:
#external DNS must contain only numbers, periods, and commas
#TODO: More serious ip address checking
if re.match('[^0-9.,]', responses["DNS_UPSTREAM"]):
errors.append(
"External DNS must contain only IP addresses and commas.")
#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):
errors.append("IP %s unable to resolve host."
% DNS_UPSTREAM)
except Exception, e:
errors.append(e)
errors.append("Not a valid IP address for External DNS: %s"
% responses["DNS_UPSTREAM"])
if len(errors) > 0:
self.parent.footer.set_text(
"Errors: %s First error: %s" % (len(errors), errors[0]))
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)
#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 responses["HOSTNAME"] in line \
or oldsettings["HOSTNAME"] 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" % (managediface_ip, responses["HOSTNAME"],
responses['DNS_DOMAIN']))
etchosts.close()
#Write dnsmasq upstream server
with open('/etc/dnsmasq.upstream', 'w') as f:
nameservers = responses['DNS_UPSTREAM'].replace(',', ' ')
f.write("nameserver %s\n" % nameservers)
f.close()
###Future feature to apply post-deployment
#Need to decide if we are pre-deployment or post-deployment
#if self.deployment == "post":
# self.updateCobbler(responses)
# services.restart("cobbler")
# def updateCobbler(self, params):
# patterns={
# 'cblr_server' : '^server: .*',
# 'cblr_next_server' : '^next_server: .*',
# 'mgmt_if' : '^interface=.*',
# 'domain' : '^domain=.*',
# 'server' : '^server=.*',
# 'dhcp-range' : '^dhcp-range=',
# 'dhcp-option' : '^dhcp-option=',
# 'pxe-service' : '^pxe-service=(^,)',
# 'dhcp-boot' : '^dhcp-boot=([^,],{3}),'
# }
def cancel(self, button):
for index, fieldname in enumerate(fields):
if fieldname == "blank":
pass
else:
self.edits[index].set_edit_text(DEFAULTS[fieldname]['value'])
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 DEFAULTS.keys():
try:
if "/" in setting:
part1, part2 = setting.split("/")
DEFAULTS[setting]["value"] = oldsettings[part1][part2]
else:
DEFAULTS[setting]["value"] = oldsettings[setting]
except:
log.warning("No setting named %s found." % setting)
continue
#Read hostname if it's already set
try:
import os
oldsettings["HOSTNAME"] = os.uname()[1]
except:
log.warning("Unable to look up system hostname")
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 ##
#log.debug(str(newsettings))
Settings().write(newsettings, defaultsfile=self.parent.settingsfile,
outfn="newsettings.yaml")
#Write naily.facts
factsettings = dict()
#log.debug(newsettings)
for key in newsettings.keys():
if key != "blank":
factsettings[key] = newsettings[key]
n = nailyfactersettings.NailyFacterSettings()
n.write(factsettings)
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update DEFAULTS
for index, fieldname in enumerate(fields):
if fieldname != "blank":
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",
DEFAULTS["TEST_DNS"]['value'],
"@%s" % server], stdout=noout,
stderr=noout)
if dns_works != 0:
return False
else:
return True
def getNetwork(self):
"""Uses netifaces module to get addr, broadcast, netmask about
network interfaces"""
import netifaces
for iface in netifaces.interfaces():
if 'lo' in iface or 'vir' in iface:
#if 'lo' in iface or 'vir' in iface or 'vbox' in iface:
if iface != "virbr2-nic":
continue
try:
self.netsettings.update({iface: netifaces.ifaddresses(
iface)[netifaces.AF_INET][0]})
self.netsettings[iface]["onboot"] = "Yes"
except:
self.netsettings.update({iface: {"addr": "", "netmask": "",
"onboot": "no"}})
self.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()
self.netsettings[iface]["link"] = content[0].strip()
except:
self.netsettings[iface]["link"] = "unknown"
#Change unknown link state to up if interface has an IP
if self.netsettings[iface]["link"] == "unknown":
if self.netsettings[iface]["addr"] != "":
self.netsettings[iface]["link"] = "up"
#Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV
try:
with open("/etc/sysconfig/network-scripts/ifcfg-%s" % iface) \
as fh:
for line in fh:
if re.match("^BOOTPROTO=", line):
self.netsettings[
iface]['bootproto'] = line.split('=').strip()
break
except:
#Let's try checking for dhclient process running for this interface
if self.getDHCP(iface):
self.netsettings[iface]['bootproto'] = "dhcp"
else:
self.netsettings[iface]['bootproto'] = "none"
def getDHCP(self, iface):
"""Returns True if the interface has a dhclient process running"""
import subprocess
noout = open('/dev/null', 'w')
dhclient_running = subprocess.call(
["pgrep", "-f", "dhclient.*%s" % (iface)],
stdout=noout, stderr=noout)
def get_default_gateway_linux(self):
"""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)))
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
self.gateway = self.get_default_gateway_linux()
self.getNetwork()
self.setNetworkDetails()
return
def refresh(self):
pass
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = urwid.Text("DNS and hostname setup")
text2 = urwid.Text("Note: Leave External DNS blank if you do not have"
" Internet access.")
self.edits = []
toolbar = self.parent.footer
for key in fields:
#for key, values in DEFAULTS.items():
#Example: key = hostname, label = Hostname, value = fuel-pm
if key == "blank":
self.edits.append(blank)
elif DEFAULTS[key]["value"] == "radio":
label = TextLabel(DEFAULTS[key]["label"])
choices = ChoicesGroup(self, ["Yes", "No"],
default_value="Yes",
fn=self.radioSelectExtIf)
self.edits.append(Columns([label, choices]))
else:
caption = DEFAULTS[key]["label"]
default = DEFAULTS[key]["value"]
tooltip = DEFAULTS[key]["tooltip"]
self.edits.append(
TextField(key, caption, 23, default, tooltip, toolbar))
#Button to check
button_check = Button("Check", self.check)
#Button to revert to previously saved settings
button_cancel = Button("Cancel", self.cancel)
#Button to apply (and check again)
button_apply = Button("Apply", self.apply)
#Wrap buttons into Columns so it doesn't expand and look ugly
check_col = Columns([button_check, button_cancel,
button_apply, ('weight', 2, blank)])
self.listbox_content = [text1, blank, text2, blank]
self.listbox_content.extend(self.edits)
self.listbox_content.append(blank)
self.listbox_content.append(check_col)
#Add listeners
#Build all of these into a list
#self.listbox_content = [ text1, blank, blank, edit1, edit2, \
# edit3, edit4, edit5, edit6, button_check ]
#Add everything into a ListBox and return it
self.listwalker = urwid.SimpleListWalker(self.listbox_content)
screen = urwid.ListBox(self.listwalker)
return screen

View File

@ -0,0 +1,496 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import copy
import socket
import struct
import re
import netaddr
import netifaces
import dhcp_checker.api
from fuelmenu.settings import *
from fuelmenu.common import network, puppet, replace, dialog
from fuelmenu.common.urwidwrapper import *
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
fields = ["blank", "ifname", "onboot", "bootproto", "ipaddr", "netmask",
"gateway"]
DEFAULTS = {
"ifname": {"label": "Interface name:",
"tooltip": "Interface system identifier",
"value": "locked"},
"onboot": {"label": "Enabled on boot:",
"tooltip": "",
"value": "radio"},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"value": "radio",
"choices": ["DHCP", "Static"]},
"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": ""},
}
YAMLTREE = "cobbler_common"
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
def fixDnsmasqUpstream(self):
#check upstream dns server
with open('/etc/dnsmasq.upstream', 'r') as f:
dnslines = f.readlines()
f.close()
nameservers = dnslines[0].split(" ")[1:]
for nameserver in nameservers:
if not self.checkDNS(nameserver):
nameservers.remove(nameserver)
if nameservers == []:
#Write dnsmasq upstream server to default if it's not readable
with open('/etc/dnsmasq.upstream', 'w') as f:
nameservers = DEFAULTS['DNS_UPSTREAM']['value'].replace(
',', ' ')
f.write("nameserver %s\n" % nameservers)
f.close()
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" % (
managediface_ip, socket.gethostname()))
def check(self, args):
"""Validate that all fields have valid values and some sanity checks"""
#Get field information
responses = dict()
self.parent.footer.set_text("Checking data...")
for index, fieldname in enumerate(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"] = "dhcp"
else:
responses["bootproto"] = "none"
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 = []
#Perform checks only if enabled
if responses["onboot"] == "no":
return responses
if responses["bootproto"] == "dhcp":
self.parent.footer.set_text("Scanning for DHCP servers. "
"Please wait...")
self.parent.refreshScreen()
try:
dhcp_server_data = dhcp_checker.api.check_dhcp_on_eth(
self.activeiface, timeout=3)
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(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)
responses["dhcp_nowait"] = True
except:
self.log.warning("dhcp_checker failed to check on %s"
% self.activeiface)
#TODO: Fix set up DHCP on down iface
responses["dhcp_nowait"] = False
#Check ipaddr, netmask, gateway only if static
if responses["bootproto"] == "none":
try:
if netaddr.valid_ipv4(responses["ipaddr"]):
ipaddr = netaddr.IPAddress(responses["ipaddr"])
else:
raise Exception("")
except:
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 Exception("")
else:
raise Exception("")
except:
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 Exception("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 Exception("Gateway IP address is not in the "
"same subnet as IP address")
except Exception, e:
errors.append(e)
if len(errors) > 0:
self.parent.footer.set_text("Errors: %s First error: %s" % (
len(errors), errors[0]))
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...")
puppetclass = "l23network::l3::ifconfig"
if responses["onboot"].lower() == "no":
params = {"ipaddr": "none"}
elif responses["bootproto"] == "dhcp":
if "dhcp_nowait" in responses.keys():
params = {"ipaddr": "dhcp",
"dhcp_nowait": responses["dhcp_nowait"]}
else:
params = {"ipaddr": "dhcp"}
else:
params = {"ipaddr": responses["ipaddr"],
"netmask": responses["netmask"],
"check_by_ping": "none"}
if len(responses["gateway"]) > 1:
params["gateway"] = responses["gateway"]
self.log.info("Puppet data: %s %s %s" % (
puppetclass, self.activeiface, params))
try:
#Gateway handling so DHCP will set gateway
if responses["bootproto"] == "dhcp":
expr = '^GATEWAY=.*'
replace.replaceInFile("/etc/sysconfig/network", expr,
"GATEWAY=")
self.parent.refreshScreen()
puppet.puppetApply(puppetclass, self.activeiface, params)
self.getNetwork()
expr = '^GATEWAY=.*'
gateway = self.get_default_gateway_linux()
if gateway is None:
gateway = ""
replace.replaceInFile("/etc/sysconfig/network", expr, "GATEWAY=%s"
% gateway)
self.fixEtcHosts()
except Exception, e:
self.log.error(e)
self.parent.footer.set_text("Error applying changes. Check logs "
"for details.")
self.getNetwork()
self.setNetworkDetails()
return False
self.parent.footer.set_text("Changes successfully applied.")
self.getNetwork()
self.setNetworkDetails()
return True
def save(self, args):
newsettings = dict()
newsettings['common'] = {YAMLTREE: {"domain": DEFAULTS['domain'][
'value']}}
for key, widget in self.edits.items():
text = widget.original_widget.get_edit_text()
newsettings['common'][YAMLTREE][key] = text
log.warning(str(newsettings))
Settings().write(newsettings, tree=YAMLTREE)
logging.warning('And this, too')
def getNetwork(self):
"""Uses netifaces module to get addr, broadcast, netmask about
network interfaces"""
for iface in netifaces.interfaces():
if 'lo' in iface or 'vir' in iface:
if iface != "virbr2-nic":
continue
try:
self.netsettings.update({iface: netifaces.ifaddresses(iface)[
netifaces.AF_INET][0]})
self.netsettings[iface]["onboot"] = "Yes"
except:
#Interface is down, so mark it onboot=no
self.netsettings.update({iface: {"addr": "", "netmask": "",
"onboot": "no"}})
self.netsettings[iface]['mac'] = netifaces.ifaddresses(iface)[
netifaces.AF_LINK][0]['addr']
self.gateway = self.get_default_gateway_linux()
#Set link state
try:
with open("/sys/class/net/%s/operstate" % iface) as f:
content = f.readlines()
self.netsettings[iface]["link"] = content[0].strip()
except:
self.netsettings[iface]["link"] = "unknown"
#Change unknown link state to up if interface has an IP
if self.netsettings[iface]["link"] == "unknown":
if self.netsettings[iface]["addr"] != "":
self.netsettings[iface]["link"] = "up"
#Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV
#default to static
self.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):
self.netsettings[iface]['bootproto'] = \
line.split('=').strip()
break
except:
#Check for dhclient process running for this interface
if self.getDHCP(iface):
self.netsettings[iface]['bootproto'] = "dhcp"
else:
self.netsettings[iface]['bootproto'] = "none"
def getDHCP(self, iface):
"""Returns True if the interface has a dhclient process running"""
import subprocess
noout = open('/dev/null', 'w')
dhclient_running = subprocess.call(["pgrep", "-f", "dhclient.*%s" %
(iface)], stdout=noout,
stderr=noout)
#self.log.info("Interface %s: %s" % (iface, dhclient_running))
if dhclient_running != 0:
return False
else:
return True
def get_default_gateway_linux(self):
"""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)))
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
self.getNetwork()
self.setNetworkDetails()
return
def radioSelectExtIf(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:
if rb.base_widget.get_label() == "Yes":
self.extdhcp = True
else:
self.extdhcp = False
break
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))
#Set text fields to current netsettings
for index, fieldname in enumerate(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):
self.getNetwork()
self.setNetworkDetails()
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = TextLabel("Network interface setup")
#Current network settings
self.net_text1 = TextLabel("")
self.net_text2 = TextLabel("")
self.net_text3 = TextLabel("")
self.net_choices = ChoicesGroup(self, sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelectIface)
self.edits = []
toolbar = self.parent.footer
for key in fields:
#Example: key = hostname, label = Hostname, value = fuel
if key == "blank":
self.edits.append(blank)
elif DEFAULTS[key]["value"] == "radio":
label = TextLabel(DEFAULTS[key]["label"])
if "choices" in DEFAULTS[key]:
choices_list = DEFAULTS[key]["choices"]
else:
choices_list = ["Yes", "No"]
choices = ChoicesGroup(self, choices_list,
default_value="Yes",
fn=self.radioSelectExtIf)
columns = Columns([('weight', 2, label),
('weight', 3, choices)])
#Attach choices rb_group so we can use it later
columns.rb_group = choices.rb_group
self.edits.append(columns)
else:
caption = DEFAULTS[key]["label"]
default = DEFAULTS[key]["value"]
tooltip = DEFAULTS[key]["tooltip"]
disabled = True if key == "ifname" else False
self.edits.append(TextField(key, caption, 23, default, tooltip,
toolbar, disabled=disabled))
#Button to check
button_check = Button("Check", self.check)
#Button to apply (and check again)
button_apply = Button("Apply", self.apply)
#Wrap buttons into Columns so it doesn't expand and look ugly
check_col = Columns([button_check, button_apply, ('weight', 3, blank)])
self.listbox_content = [text1, blank]
self.listbox_content.extend([self.net_choices, self.net_text1,
self.net_text2, self.net_text3,
blank])
self.listbox_content.extend(self.edits)
self.listbox_content.append(blank)
self.listbox_content.append(check_col)
#Add everything into a ListBox and return it
self.listwalker = urwid.SimpleListWalker(self.listbox_content)
screen = urwid.ListBox(self.listwalker)
self.setNetworkDetails()
self.screen = screen
return screen

145
fuelmenu/modules/mirrors.py Normal file
View File

@ -0,0 +1,145 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
import logging
import sys
import copy
from fuelmenu.settings import *
from fuelmenu.common.urwidwrapper import *
log = logging.getLogger('fuelmenu.mirrors')
log.info("test")
blank = urwid.Divider()
DEFAULTS = {
"custom_mirror": "http://mirror.your-company-name.com/",
"parent_proxy": "",
"port": "3128"
}
class mirrors(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Repo Mirrors"
self.priority = 25
self.visible = False
self.parent = parent
self.listbox_content = []
self.settings = copy.deepcopy(DEFAULTS)
self.screen = None
#self.screen = self.screenUI()
def apply(self, args):
if not self.check(args):
log.error("Check failed. Not applying")
return False
conf = Settings()
conf.write(module="mirrors", values=self.settings)
def check(self, args):
log = logging.getLogger('fuelmenu.mirrors')
customurl = self.edit1.get_edit_text()
self.parent.footer.set_text("Checking %s" % customurl)
log.info("Checking %s" % customurl)
if self.repochoice == "Defult":
self.parent.footer.set_text("")
pass
else:
#Ensure host can connect
import subprocess
reachable = subprocess.call(["curl", "-o", "/dev/null", "--silent",
"--head", "--write-out",
"'%{http_code}\n'", customurl])
error_msg = None
if reachable == 0:
pass
elif reachable == 1 or reachable == 3:
error_msg = u"Unrecognized protocol. Did you spell it right?"
elif reachable == 6:
error_msg = u"Couldn't resolve host."
elif reachable == 7:
error_msg = u"Couldn't connect to host."
elif reachable == 6:
error_msg = u"Couldn't resolve host."
if error_msg:
self.parent.footer.set_text(
"Could not reach custom mirror. Error: %s" % (error_msg))
return False
self.parent.footer.set_text("Reached custom mirror!")
#Ensure valid page with 2XX or 3XX return code
status_code = subprocess.check_output(["curl", "-o", "/dev/null",
"--silent", "--head",
"--write-out",
"'\%{http_code}'",
customurl])
import re
regexp = re.compile(r'[23]\d\d')
if regexp.search(status_code) is not None:
error_msg = "URL not reachable on server. Error %s" \
% status_code
log.error("Could not reach custom url %s. Error code: %s" %
(customurl, reachable))
self.parent.footer.set_text("Could not reach custom url %s.\
Error code: %s" % (customurl, reachable))
return False
self.parent.footer.set_text("Repo mirror OK!")
return True
def radioSelect(self, current, state, user_data=None):
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.repochoice = rb.base_widget.get_label()
break
#def keypress(self, size, key):
# self.parent.footer.set_text("keypress")
#def displayTooltip(self, obj):
# focus = obj.get_focus()[0].content
# self.parent.footer.set_text(focus.get_label())
def refresh(self):
pass
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = TextLabel(u"Choose repo mirrors to use.\n"
u"Note: Refer to Fuel documentation on how to set "
u" up a custom mirror.")
choice_list = [u"Default", u"Custom"]
self.choices = ChoicesGroup(self, choice_list)
self.repochoice = "Default"
self.edit1 = TextField("custom_mirror", "Custom URL:", 15,
DEFAULTS["custom_mirror"],
"URL goes here", self.parent.footer)
self.edit2 = TextField("parent_proxy", "Squid parent proxy:", 20,
DEFAULTS["parent_proxy"],
"Squid proxy URL (include http://)",
self.parent.footer)
self.edit3 = TextField("port", "Port:", 5, DEFAULTS["parent_proxy"],
"Squid Proxy port (usually 3128)",
self.parent.footer)
self.proxyedits = Columns([('weight', 3, self.edit2), self.edit3])
#Button to check
button_check = Button("Check", self.check)
#Button to apply (and check again)
button_apply = Button("Apply", self.apply)
#Wrap into Columns so it doesn't expand and look ugly
check_col = Columns([button_check, button_apply, ('weight', 7, blank)])
#Build all of these into a list
self.listbox_content = [text1, blank, blank, self.choices, blank,
self.edit1, blank, self.proxyedits, blank,
blank, check_col]
#Add everything into a ListBox and return it
walker = urwid.SimpleListWalker(self.listbox_content)
#urwid.connect_signal(walker, 'modified', self.displayTooltip)
self.myscreen = urwid.ListBox(walker)
return self.myscreen

46
fuelmenu/modules/shell.py Normal file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
import urwid
import urwid.raw_display
import urwid.web_display
from fuelmenu.common.urwidwrapper import *
import subprocess
import os
import pty
blank = urwid.Divider()
class shell():
def __init__(self, parent):
self.name = "Shell Login"
self.priority = 99
self.visible = True
self.parent = parent
self.screen = None
#self.screen = self.screenUI()
def check(self):
#TODO: Ensure all params are filled out and sensible
return True
def start_shell(self, args):
self.parent.mainloop.screen.stop()
message = "Type exit to return to the main UI."
subprocess.call("clear ; echo '%s';echo;bash -i" % message, shell=True)
self.parent.mainloop.screen.start()
def refresh(self):
pass
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = urwid.Text("Press the button below to enter a shell login.")
login_button = Button("Shell Login", self.start_shell)
#Build all of these into a list
listbox_content = [text1, blank, login_button]
#Add everything into a ListBox and return it
screen = urwid.ListBox(urwid.SimpleListWalker(listbox_content))
return screen

102
fuelmenu/modules/welcome.py Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urwid
import urwid.raw_display
import urwid.web_display
from fuelmenu.common.urwidwrapper import *
blank = urwid.Divider()
class welcome():
def __init__(self, parent):
self.name = "Welcome"
self.priority = 1
self.visible = False
self.screen = self.screenUI()
def check(self):
#TODO: Ensure all params are filled out and sensible
return True
def refresh(self):
pass
def screenUI(self):
#Define your text labels, text fields, and buttons first
text1 = urwid.Text("Welcome to Fuel! Use the menu on the left")
fuellogo_huge = [
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYY'),('red','YYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYY'),('red','YYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYY'),('red','YYYY'),('light gray','YYYYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYY'),('light gray','YYYY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YY')],
[('light gray','YY'),('red','YYYYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYYYYYYYYYY'),('light gray','YYYYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYYYY'),('light gray','YY')],
[('light gray','YYYY'),('red','YYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('red','YYYYYYYYYYYY'),('light gray','YYYYYYYYYYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYY'),('light gray','YYYYYYYY'),('red','YYYYYYYYYYYYYYYYYYYYYY'),('light gray','YYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYY'),('black','YYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YYYYYY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYYYY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYYYY')],
[('light gray','YY'),('black','YYYYYY'),('light gray','YYYY'),('black','YYYY'),('light gray','YYYY'),('black','YYYY'),('light gray','YYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YYYYYY'),('light gray','YYYYYY'),('black','YYYY'),('light gray','YYYY'),('black','YYYYYY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYY'),('black','YYYYYY'),('light gray','YY'),('black','YYYYYY'),('light gray','YYYYYY'),('black','YYYY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YYYYYYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YYYYYYYY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYY'),('black','YYYYYY'),('light gray','YY'),('black','YY'),('light gray','YYYYYY'),('black','YYYY'),('light gray','YYYYYY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYYYY'),('black','YY'),('light gray','YY'),('black','YY'),('light gray','YYYY')],
[('light gray','YYYY'),('black','YY'),('light gray','YYYYYY'),('black','YYYY'),('light gray','YYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYY'),('black','YYYYYY'),('light gray','YYYY'),('black','YYYYYY'),('light gray','YYYYYY'),('black','YYYYYY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY'),('black','YYYYYY'),('light gray','YYYYYY'),('black','YY'),('light gray','YYYY'),('black','YYYYYY'),('light gray','YYYY'),('black','YYYY'),('light gray','YY'),('black','YY'),('light gray','YYYY'),('black','YY'),('light gray','YY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),('black','YY'),('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
[('light gray','YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')],
]
fuellogo_small=[
[('red',u'±ÛÛÛÛÛÛÛÛÛÛ± ±ÛÛ± ±ÛÛ± ±ÛÛÛÛÛÛÛÛÛ± ±ÛÛ± TM')],
[('red',u'Û²²²²²²²²²²Û Û²²² Û²²² Û²²²²²²²²²² Û²²²')],
[('red',u'Û²²²²²²²²²²° Û²²² Û²²² Û²²²²²²²²²± Û²²²')],
#[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
#[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
[('red',u'Û²²²ÛÛÛÛÛ Û²²² Û²²² Û²²²ÛÛÛÛÛ Û²²²')],
[('red',u'Û²²²²²²²² Û²²² Û²²² Û²²²²²²²² Û²²²')],
#[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
#[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
[('red',u'Û²²² Û²²² Û²²² Û²²² Û²²²')],
[('red',u'Û²²² Û²²²Û Û²²²² Û²²² Û²²²')],
[('red',u'Û²²² ±²²²²ÛÛÛ²²²²° Û²²²ÛÛÛÛÛÛ° Û²²²ÛÛÛÛÛÛÛ°')],
[('red',u'Û²²² ²²²²²²²²²²² Û²²²²²²²²²Û Û²²²²²²²²²²Û')],
[('red',u'±²²° °²²²²²²²° ±²²²²²²²²²° ±²²²²²²²²²²°')],
#[''],
#[''],
[('light gray',u' ÛÛ ÛÛÛ ÛÛÛ Û R')],
[('light gray',u' Û Û Û Û Û Û ')],
[('light gray',u'ÛÛÛ ÛÛ ÛÛÛ Û Û ÛÛÛ ÛÛÛÛ ÛÛÛÛ Û ÛÛÛ ÛÛ ÛÛ Û Û')],
[('light gray',u' Û Û Û Û Û Û Û Û Û Û Û Û ÛÛ Û Û Û Û Û')],
[('light gray',u' Û Û Û Û Û Û Û Û ÛÛÛÛ Û Û Û Û ÛÛÛ Û ÛÛ')],
[('light gray',u' Û Û Û Û Û Û Û Û Û Û Û Û Û Û Û Û Û Û')],
[('light gray',u' Û ÛÛ Û ÛÛÛ ÛÛÛ ÛÛÛÛ Û Û ÛÛÛ ÛÛ ÛÛÛ ÛÛ Û Û')],
[('light gray',u' Û')],
[('light gray',u' Û')],
]
logotexts=[]
for line in fuellogo_small:
logotexts.append(TextLabel(line))
#Build all of these into a list
listbox_content = [ text1 ] + logotexts
#Add everything into a ListBox and return it
screen = urwid.ListBox(urwid.SimpleListWalker(listbox_content))
return screen

View File

@ -0,0 +1,7 @@
mnbs_internal_interface=eth0
mnbs_internal_ipaddress=10.20.0.2
mnbs_internal_netmask=255.255.255.0
mnbs_static_pool_start=10.20.0.130
mnbs_static_pool_end=10.20.0.250
mnbs_dhcp_pool_start=10.20.0.10
mnbs_dhcp_pool_end=10.20.0.120

110
fuelmenu/settings.py Normal file
View File

@ -0,0 +1,110 @@
import yaml
import collections
try:
from collections import OrderedDict
except:
# python 2.6 or earlier use backport
from ordereddict import OrderedDict
def construct_ordered_mapping(self, node, deep=False):
if not isinstance(node, yaml.MappingNode):
raise 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 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():
def __init__(self):
pass
def read(self, yamlfile):
try:
infile = file(yamlfile, 'r')
settings = yaml.load(infile)
return settings
except:
import logging
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_flow_style=False)
return True
if __name__ == '__main__':
import textwrap
sample = """
one:
two: fish
red: fish
blue: fish
two:
a: yes
b: no
c: null
"""
infile = file('settings.yaml', 'r')
data = yaml.load(infile)
#data = yaml.load(infile, OrderedDictYAMLLoader)
#data = yaml.load(textwrap.dedent(sample), OrderedDictYAMLLoader)
outfile = file("testout", 'w')
yaml.dump(data, outfile, default_flow_style=False)
#assert type(data) is OrderedDict
print data.items()

15
fuelmenu/settings.yaml Normal file
View File

@ -0,0 +1,15 @@
HOSTNAME: "fuel"
DNS_DOMAIN: "domain.tld"
DNS_SEARCH: "domain.tld"
DNS_UPSTREAM: "8.8.8.8"
ADMIN_NETWORK:
interface: "eth0"
cidr: "10.20.0.0/24"
netmask: "255.255.255.0"
size: "256"
static_start: "10.20.0.10"
static_end: "10.20.0.120"
first: "10.20.0.130"
last: "10.20.0.254"

55
setup.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/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='0.1',
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',
'ethtool>=0.6',
#'pypcap',
'urwid>=1.1.1',
],
include_package_data=True,
packages=setuptools.find_packages(),
entry_points={
'console_scripts': [
'fuelmenu = fuelmenu.fuelmenu:setup',
],
},
)