diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..50801ab --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include fuelmenu *.py *.yaml *.default +include setup.py +include README +#recursive-include fuelmenu * diff --git a/README b/README new file mode 100644 index 0000000..97d4159 --- /dev/null +++ b/README @@ -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. + diff --git a/fuelmenu/__init__.py b/fuelmenu/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fuelmenu/common/__init__.py b/fuelmenu/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fuelmenu/common/dialog.py b/fuelmenu/common/dialog.py new file mode 100644 index 0000000..ab022c6 --- /dev/null +++ b/fuelmenu/common/dialog.py @@ -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 diff --git a/fuelmenu/common/nailyfactersettings.py b/fuelmenu/common/nailyfactersettings.py new file mode 100644 index 0000000..c67fefb --- /dev/null +++ b/fuelmenu/common/nailyfactersettings.py @@ -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 diff --git a/fuelmenu/common/network.py b/fuelmenu/common/network.py new file mode 100644 index 0000000..0038e81 --- /dev/null +++ b/fuelmenu/common/network.py @@ -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 diff --git a/fuelmenu/common/puppet.py b/fuelmenu/common/puppet.py new file mode 100644 index 0000000..22ba483 --- /dev/null +++ b/fuelmenu/common/puppet.py @@ -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 diff --git a/fuelmenu/common/replace.py b/fuelmenu/common/replace.py new file mode 100644 index 0000000..fc46bf3 --- /dev/null +++ b/fuelmenu/common/replace.py @@ -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() diff --git a/fuelmenu/common/urwidwrapper.py b/fuelmenu/common/urwidwrapper.py new file mode 100644 index 0000000..a9edcc7 --- /dev/null +++ b/fuelmenu/common/urwidwrapper.py @@ -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 ) diff --git a/fuelmenu/fuelmenu.py b/fuelmenu/fuelmenu.py new file mode 100755 index 0000000..c511dc6 --- /dev/null +++ b/fuelmenu/fuelmenu.py @@ -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() diff --git a/fuelmenu/modules/cobblerconf.py b/fuelmenu/modules/cobblerconf.py new file mode 100644 index 0000000..5268196 --- /dev/null +++ b/fuelmenu/modules/cobblerconf.py @@ -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(" 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(" 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("=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', + ], + }, +) +