move fuelmenu to base level
This commit is contained in:
parent
6041bf10c9
commit
d2401ec3e1
|
@ -0,0 +1,4 @@
|
|||
recursive-include fuelmenu *.py *.yaml *.default
|
||||
include setup.py
|
||||
include README
|
||||
#recursive-include fuelmenu *
|
|
@ -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,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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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 )
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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"
|
||||
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
Loading…
Reference in New Issue