Deprecate fuelmenu directory

Change-Id: Ia6affb462ada7b09e531e9cd096a2e109ac51a9a
Related-Bug: #1506885
This commit is contained in:
Vladimir Kozhukalov 2015-10-23 18:35:30 +03:00
parent 92596c1927
commit 3119b6f5bc
35 changed files with 0 additions and 4202 deletions

View File

@ -26,11 +26,6 @@ description:
maintainers:
- fuelmenu/:
- name: Matthew Mosesohn
email: mmosesohn@mirantis.com
IRC: mattymo
- fuel_upgrade_system/fuel_package_updates/:
- name: Matthew Mosesohn
email: mmosesohn@mirantis.com

View File

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

View File

@ -1,25 +0,0 @@
Fuel menu
This tool is used to perform setup of network interfaces, as well as configure
Cobbler parameters. The framework is extensible.
Plugin guidelines:
Create a python class with a filename matching the class:
class foo(urwid.Widget) and foo.py
Place this file in the Fuel Menu modules folder.
Plugin class should define the following functions:
__init__(self, parent)
check(self, args)
apply(self, args)
save(self) #Still need to complete
load(self) #Still need to complete
screenUI(self)
screenUI should use urwidwrapper class to define and set up all UI elements
Note that you need to specify a function for buttons and radio button groups
for them to work properly. Check and Apply buttons should point to check and
apply functions, respectively.

View File

@ -1,13 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,13 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -1,61 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fuelmenu.common.urwidwrapper as widget
import urwid
import urwid.raw_display
import urwid.web_display
blank = urwid.Divider()
class ModalDialog(urwid.WidgetWrap):
signals = ['close']
title = None
def __init__(self, title, body, escape_key, previous_widget, loop=None):
self.escape_key = escape_key
self.previous_widget = previous_widget
self.keep_open = True
self.loop = loop
if type(body) in [str, unicode]:
body = urwid.Text(body)
self.title = title
bodybox = urwid.LineBox(urwid.Pile([body, blank,
widget.Button("Close", self.close)]), title)
overlay = urwid.Overlay(urwid.Filler(bodybox), previous_widget,
'center', ('relative', 80), 'middle',
('relative', 80))
overlay_attrmap = urwid.AttrMap(overlay, "body")
super(ModalDialog, self).__init__(overlay_attrmap)
def close(self, arg):
urwid.emit_signal(self, "close")
self.keep_open = False
self.loop.widget = self.previous_widget
def __repr__(self):
return "<%s title='%s' at %s>" % (self.__class__.__name__, self.title,
hex(id(self)))
def display_dialog(self, body, title, escape_key="esc"):
filler = urwid.Pile([body])
dialog = ModalDialog(title, filler, escape_key,
self.parent.mainloop.widget,
loop=self.parent.mainloop)
self.parent.mainloop.widget = dialog
return dialog

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class NetworkException(Exception):
pass
class BadIPException(Exception):
pass

View File

@ -1,216 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fuelmenu.common.urwidwrapper as widget
from fuelmenu.settings import Settings
import logging
import netifaces
import re
import socket
import struct
import subprocess
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.modulehelper')
blank = urwid.Divider()
class ModuleHelper(object):
@classmethod
def load(cls, modobj):
#Read in yaml
defaultsettings = Settings().read(modobj.parent.defaultsettingsfile)
oldsettings = defaultsettings.copy()
oldsettings.update(Settings().read(modobj.parent.settingsfile))
for setting in modobj.defaults.keys():
if "label" in setting:
continue
elif "/" in setting:
part1, part2 = setting.split("/")
modobj.defaults[setting]["value"] = oldsettings[part1][part2]
else:
modobj.defaults[setting]["value"] = oldsettings[setting]
if modobj.netsettings and oldsettings["ADMIN_NETWORK"]["interface"] \
in modobj.netsettings.keys():
modobj.activeiface = oldsettings["ADMIN_NETWORK"]["interface"]
return oldsettings
@classmethod
def save(cls, modobj, responses):
newsettings = dict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
newsettings[part1] = modobj.oldsettings[part1]
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
return newsettings
@classmethod
def cancel(self, cls, button=None):
for index, fieldname in enumerate(cls.fields):
if fieldname != "blank" and "label" not in fieldname:
try:
cls.edits[index].set_edit_text(cls.defaults[fieldname][
'value'])
except AttributeError:
log.warning("Field %s unable to reset text" % fieldname)
@classmethod
def screenUI(cls, modobj, headertext, fields, defaults,
showallbuttons=False, buttons_visible=True):
log.debug("Preparing screen UI for %s" % modobj.name)
#Define text labels, text fields, and buttons first
header_content = []
for text in headertext:
if isinstance(text, str):
header_content.append(urwid.Text(text))
else:
header_content.append(text)
edits = []
toolbar = modobj.parent.footer
for key in fields:
#Example: key = hostname, label = Hostname, value = fuel-pm
if key == "blank":
edits.append(blank)
elif defaults[key]["value"] == "radio":
label = widget.TextLabel(defaults[key]["label"])
if "choices" in defaults[key]:
choices_list = defaults[key]["choices"]
else:
choices_list = ["Yes", "No"]
choices = widget.ChoicesGroup(choices_list,
default_value="Yes",
fn=modobj.radioSelect)
columns = widget.Columns([('weight', 2, label),
('weight', 3, choices)])
#Attach choices rb_group so we can use it later
columns.rb_group = choices.rb_group
edits.append(columns)
elif defaults[key]["value"] == "label":
edits.append(widget.TextLabel(defaults[key]["label"]))
else:
ispassword = "PASSWORD" in key.upper()
caption = defaults[key]["label"]
default = defaults[key]["value"]
tooltip = defaults[key]["tooltip"]
edits.append(
widget.TextField(key, caption, 23, default, tooltip,
toolbar, ispassword=ispassword))
listbox_content = []
listbox_content.extend(header_content)
listbox_content.append(blank)
listbox_content.extend(edits)
listbox_content.append(blank)
#Wrap buttons into Columns so it doesn't expand and look ugly
if buttons_visible:
#Button to check
button_check = widget.Button("Check", modobj.check)
#Button to revert to previously saved settings
button_cancel = widget.Button("Cancel", modobj.cancel)
#Button to apply (and check again)
button_apply = widget.Button("Apply", modobj.apply)
if modobj.parent.globalsave and showallbuttons is False:
check_col = widget.Columns([button_check])
else:
check_col = widget.Columns([button_check, button_cancel,
button_apply, ('weight', 2, blank)])
listbox_content.append(check_col)
#Add everything into a ListBox and return it
listwalker = widget.TabbedListWalker(listbox_content)
screen = urwid.ListBox(listwalker)
modobj.edits = edits
modobj.walker = listwalker
modobj.listbox_content = listbox_content
return screen
@classmethod
def getNetwork(cls, modobj):
"""Returns addr, broadcast, netmask for each network interface."""
re_ifaces = re.compile(r"lo|vir|vbox|docker|veth")
for iface in netifaces.interfaces():
if re_ifaces.search(iface):
continue
try:
modobj.netsettings.update({iface: netifaces.ifaddresses(iface)[
netifaces.AF_INET][0]})
modobj.netsettings[iface]["onboot"] = "Yes"
except (TypeError, KeyError):
modobj.netsettings.update({iface: {"addr": "", "netmask": "",
"onboot": "no"}})
modobj.netsettings[iface]['mac'] = netifaces.ifaddresses(iface)[
netifaces.AF_LINK][0]['addr']
#Set link state
try:
with open("/sys/class/net/%s/operstate" % iface) as f:
content = f.readlines()
modobj.netsettings[iface]["link"] = content[0].strip()
except IOError:
log.warning("Unable to read operstate file for %s" % iface)
modobj.netsettings[iface]["link"] = "unknown"
#Change unknown link state to up if interface has an IP
if modobj.netsettings[iface]["link"] == "unknown":
if modobj.netsettings[iface]["addr"] != "":
modobj.netsettings[iface]["link"] = "up"
#Read bootproto from /etc/sysconfig/network-scripts/ifcfg-DEV
modobj.netsettings[iface]['bootproto'] = "none"
try:
with open("/etc/sysconfig/network-scripts/ifcfg-%s" % iface)\
as fh:
for line in fh:
if re.match("^BOOTPROTO=", line):
modobj.netsettings[iface]['bootproto'] = \
line.split('=')[1].strip()
break
except Exception:
#Check for dhclient process running for this interface
if modobj.getDHCP(iface):
modobj.netsettings[iface]['bootproto'] = "dhcp"
else:
modobj.netsettings[iface]['bootproto'] = "none"
modobj.gateway = modobj.get_default_gateway_linux()
@classmethod
def getDHCP(cls, iface):
"""Returns True if the interface has a dhclient process running."""
noout = open('/dev/null', 'w')
dhclient_running = subprocess.call(["pgrep", "-f", "dhclient.*%s" %
(iface)], stdout=noout,
stderr=noout)
return (dhclient_running == 0)
@classmethod
def get_default_gateway_linux(cls):
"""Read the default gateway directly from /proc."""
with open("/proc/net/route") as fh:
for line in fh:
fields = line.strip().split()
if fields[1] != '00000000' or not int(fields[3], 16) & 2:
continue
return socket.inet_ntoa(struct.pack("<L", int(fields[2], 16)))

View File

@ -1,107 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common.errors import BadIPException
from fuelmenu.common.errors import NetworkException
import netaddr
import subprocess
def inSameSubnet(ip1, ip2, netmask_or_cidr):
try:
cidr1 = netaddr.IPNetwork("%s/%s" % (ip1, netmask_or_cidr))
cidr2 = netaddr.IPNetwork("%s/%s" % (ip2, netmask_or_cidr))
return cidr1 == cidr2
except netaddr.AddrFormatError:
return False
def getCidr(ip, netmask):
try:
ipn = netaddr.IPNetwork("%s/%s" % (ip, netmask))
return str(ipn.cidr)
except netaddr.AddrFormatError:
return False
def getCidrSize(cidr):
try:
ipn = netaddr.IPNetwork(cidr)
return ipn.size
except netaddr.AddrFormatError:
return False
def getNetwork(ip, netmask, additionalip=None):
#Return a list excluding ip and broadcast IPs
try:
ipn = netaddr.IPNetwork("%s/%s" % (ip, netmask))
ipn_list = list(ipn)
#Drop broadcast and network ip
ipn_list = ipn_list[1:-1]
#Drop ip
ipn_list[:] = [value for value in ipn_list if str(value) != ip]
#Drop additionalip
if additionalip:
ipn_list[:] = [value for value in ipn_list if
str(value) != additionalip]
return ipn_list
except netaddr.AddrFormatError:
return False
def range(startip, endip):
#Return a list of IPs between startip and endip
try:
return set(netaddr.iter_iprange(startip, endip))
except netaddr.AddrFormatError:
raise BadIPException("Invalid IP address(es) specified.")
def intersects(range1, range2):
#Returns true if any IPs in range1 exist in range2
return range1 & range2
def netmaskToCidr(netmask):
return sum([bin(int(x)).count('1') for x in netmask.split('.')])
def duplicateIPExists(ip, iface, arping_bind=False):
"""Checks for duplicate IP addresses using arping
Don't use arping_bind unless you know what you are doing.
:param ip: IP to scan for
:param iface: Interface on which to send requests
:param arping_bind: Bind to IP when probing (IP must be already assigned.)
:returns: boolean
"""
noout = open('/dev/null', 'w')
if arping_bind:
bind_ip = ip
else:
bind_ip = "0.0.0.0"
no_dupes = subprocess.call(["arping", "-D", "-c3", "-w1", "-I", iface,
"-s", bind_ip, ip], stdout=noout, stderr=noout)
return (no_dupes != 0)
def upIface(iface):
noout = open('/dev/null', 'w')
result = subprocess.call(["ifconfig", iface, "up"], stdout=noout,
stderr=noout)
if result != 0:
raise NetworkException("Failed to up interface {0}".format(iface))

View File

@ -1,80 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import subprocess
#Python 2.6 hack to add check_output command
if "check_output" not in dir(subprocess): # duck punch it in!
def f(*popenargs, **kwargs):
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, \
itwill be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs,
**kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise Exception(retcode, cmd)
return output
subprocess.check_output = f
def puppetApply(classes):
#name should be a string
#params should be a dict or list of dicts
'''Runs puppet apply -e "classname {'name': params}".'''
log = logging
log.info("Puppet start")
command = ["puppet", "apply", "-d", "-v", "--logdest",
"/var/log/puppet/fuelmenu-puppet.log"]
input = []
for cls in classes:
if cls['type'] == "resource":
input.extend([cls["class"], "{", '"%s":' % cls["name"]])
elif cls['type'] == "class":
input.extend(["class", "{", '"%s":' % cls["class"]])
else:
log.error("Invalid type %s" % cls['type'])
return False
#Build params
for key, value in cls["params"].iteritems():
if type(value) == bool:
input.extend([key, "=>", '%s,' % str(value).lower()])
else:
input.extend([key, "=>", '"%s",' % value])
input.append('}')
log.debug(' '.join(command))
log.debug(' '.join(input))
output = ""
try:
process = subprocess.Popen(command, stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
output, errout = process.communicate(input=' '.join(input))
except Exception as e:
import traceback
log.error(traceback.print_exc())
if "err:" in output:
log.error(e)
return False
else:
log.debug(output)
return True

View File

@ -1,25 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from random import choice
import string
def password(arg=None):
try:
length = int(arg)
except Exception:
length = 8
chars = string.letters + string.digits
return ''.join([choice(chars) for _ in xrange(length)])

View File

@ -1,23 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
def replaceInFile(filename, orig, new):
lines = open(filename).readlines()
for lineno, line in enumerate(lines):
lines[lineno] = re.sub(orig, new, line)
with open(filename, 'w') as f:
f.write("".join(lines))

View File

@ -1,40 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import thread
from threading import Timer
class TimeoutError(Exception):
pass
def handler(signum, frame):
raise TimeoutError('Timeout error')
def run_with_timeout(check, args=None, kwargs=None, timeout=60, default=False):
args = args if args else []
kwargs = kwargs if kwargs else dict()
if not timeout:
return check(*args, **kwargs)
try:
timeout_timer = Timer(timeout, thread.interrupt_main)
timeout_timer.start()
result = check(*args, **kwargs)
return result
except KeyboardInterrupt:
return default
finally:
timeout_timer.cancel()

View File

@ -1,270 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.urwidwrapper')
def TextField(keyword, label, width, default_value=None, tooltip=None,
toolbar=None, disabled=False, ispassword=False):
"""Returns an Urwid Edit object."""
if ispassword:
mask = "*"
else:
mask = None
if not tooltip:
edit_obj = urwid.Edit(('important', label.ljust(width)), default_value,
mask=mask)
else:
edit_obj = TextWithTip(('important', label.ljust(width)),
default_value, tooltip, toolbar, mask=mask)
wrapped_obj = urwid.AttrWrap(edit_obj, 'editbx', 'editfc')
if disabled:
wrapped_obj = urwid.WidgetDisable(urwid.AttrWrap(edit_obj,
'important', 'editfc'))
#Add get_edit_text and set_edit_text to wrapped_obj so we can use later
wrapped_obj.set_edit_text = edit_obj.set_edit_text
wrapped_obj.get_edit_text = edit_obj.get_edit_text
return wrapped_obj
def ChoicesGroup(choices, default_value=None, fn=None):
"""Returns list of RadioButtons in a one-line GridFlow."""
rb_group = []
for txt in choices:
is_default = True if txt == default_value else False
urwid.AttrWrap(urwid.RadioButton(rb_group, txt,
is_default, on_state_change=fn,
user_data=txt),
'buttn', 'buttnf')
wrapped_choices = TabbedGridFlow(rb_group, 13, 3, 0, 'left')
#Bundle rb_group so it can be used later easily
wrapped_choices.rb_group = rb_group
return wrapped_choices
def TextLabel(text):
"""Returns an Urwid text object."""
return urwid.Text(text)
def Columns(objects):
"""Returns a padded Urwid Columns object that is left aligned."""
# Objects is a list of widgets. Widgets may be optionally specified
# as a tuple with ('weight', weight, widget) or (width, widget).
# Tuples without a widget have a weight of 1."""
return urwid.Padding(TabbedColumns(objects, 1),
left=0, right=0, min_width=61)
def Button(text, fn):
"""Returns a wrapped Button with reverse focus attribute."""
button = urwid.Button(text, fn)
return urwid.AttrMap(button, None, focus_map='reversed')
class TabbedGridFlow(urwid.GridFlow):
def __init__(self, cells, cell_width, h_sep, v_sep, align):
urwid.GridFlow.__init__(self, cells=cells, cell_width=cell_width,
h_sep=h_sep, v_sep=v_sep, align=align)
def keypress(self, size, key):
if key == 'tab' and self.focus_position < (len(self.contents) - 1)\
and self.contents[self.focus_position + 1][0].selectable():
self.tab_next(self.focus_position)
elif key == 'shift tab' and self.focus_position > 0 \
and self.contents[self.focus_position - 1][0].selectable():
self.tab_prev(self.focus_position)
else:
return self.__super.keypress(size, key)
def tab_next(self, pos):
self.set_focus(pos + 1)
maxlen = (len(self.contents) - 1)
while pos < maxlen:
if self.contents[pos][0].selectable():
return
else:
pos += 1
if pos >= maxlen:
pos = 0
self.set_focus(pos)
def tab_prev(self, pos):
self.set_focus(pos - 1)
while pos > 0:
if self.contents[pos][0].selectable():
return
else:
pos -= 1
if pos == 0:
pos = (len(self.contents) - 1)
self.set_focus(pos)
def first_selectable(self):
'''returns index of first selectable widget in contents.'''
for pos, item in enumerate(self.contents):
if item[0].selectable():
return pos
return (len(self.contents) - 1)
class TabbedColumns(urwid.Columns):
def __init__(self, widget_list, dividechars=0, focus_column=None,
min_width=1, box_columns=None):
urwid.Columns.__init__(self, widget_list,
dividechars=dividechars,
focus_column=focus_column,
min_width=min_width,
box_columns=box_columns)
def keypress(self, size, key):
if key == 'tab' and self.focus_position < (len(self.contents) - 1)\
and self.widget_list[self.focus_position + 1].selectable():
self.tab_next(self.focus_position)
elif key == 'shift tab' and self.focus_position > 0 \
and self.widget_list[self.focus_position - 1].selectable():
self.tab_prev(self.focus_position)
else:
return self.__super.keypress(size, key)
def tab_next(self, pos):
self.set_focus(pos + 1)
maxlen = (len(self.contents) - 1)
while pos < maxlen:
if self.widget_list[pos].selectable():
return
else:
pos += 1
if pos >= maxlen:
pos = 0
self.set_focus(pos)
def tab_prev(self, pos):
self.set_focus(pos - 1)
while pos > 0:
if self.widget_list[pos].selectable():
return
else:
pos -= 1
if pos == 0:
pos = (len(self.widget_list) - 1)
self.set_focus(pos)
def first_selectable(self):
'''returns index of first selectable widget in widget_list.'''
for pos, item in enumerate(self.widget_list):
if item.selectable():
return pos
return (len(self.widget_list) - 1)
class TextWithTip(urwid.Edit):
def __init__(self, label, default_value=None, tooltip=None, toolbar=None,
mask=None):
urwid.Edit.__init__(self, caption=label, edit_text=default_value,
mask=mask)
self.tip = tooltip
self.toolbar = toolbar
def render(self, size, focus=False):
if focus:
self.toolbar.set_text(self.tip)
canv = super(TextWithTip, self).render(size, focus)
return canv
class TabbedListWalker(urwid.ListWalker):
def __init__(self, lst):
self.lst = lst
self.focus = 0
def _modified(self):
return urwid.ListWalker._modified(self)
def tab_next(self):
item, pos = self.get_next(self.focus)
while pos is not None:
if item.selectable():
break
else:
item, pos = self.get_next(pos)
if pos is None:
pos = 0
self.focus = pos
self._modified()
try:
#Reset focus to first selectable widget in item
if hasattr(item, 'original_widget'):
item.original_widget.set_focus(
item.original_widget.first_selectable())
else:
item.set_focus(item.first_selectable())
except Exception:
#Ignore failure. Case only applies to TabbedColumns and
#TabbedGridFlow. Other items should fail silently.
pass
def tab_prev(self):
item, pos = self.get_prev(self.focus)
while pos is not None:
if item.selectable():
break
else:
item, pos = self.get_prev(pos)
if pos is None:
pos = (len(self.lst) - 1)
self.focus = pos
self._modified()
try:
if hasattr(item, 'original_widget'):
item.original_widget.set_focus(
len(item.original_widget.contents) - 1)
else:
item.set_focus(len(item.contents) - 1)
except Exception:
#Ignore failure. Case only applies to TabbedColumns and
#TabbedGridFlow. Other items should fail silently.
pass
def get_focus(self):
if self.lst:
return self.lst[self.focus], self.focus
else:
return None, None
def set_focus(self, focus):
self.focus = focus
def get_next(self, pos):
if (pos + 1) >= len(self.lst):
return None, None
return self.lst[pos + 1], pos + 1
def get_prev(self, pos):
if (pos - 1) < 0:
return None, None
return self.lst[pos - 1], pos - 1

View File

@ -1,479 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from common import dialog
from common import timeout
from common import urwidwrapper as widget
import dhcp_checker.api
import dhcp_checker.utils
import logging
import operator
from optparse import OptionParser
import os
from settings import Settings
import signal
import subprocess
import sys
import traceback
import urwid
import urwid.raw_display
import urwid.web_display
# set up logging
logging.basicConfig(filename='/var/log/fuelmenu.log',
format="%(asctime)s %(levelname)s %(message)s",
level=logging.DEBUG)
log = logging.getLogger('fuelmenu.loader')
class Loader(object):
def __init__(self, parent):
self.modlist = []
self.choices = []
self.child = None
self.children = []
self.childpage = None
self.parent = parent
def load_modules(self, module_dir):
if module_dir not in sys.path:
sys.path.append(module_dir)
modules = [os.path.splitext(f)[0] for f in os.listdir(module_dir)
if f.endswith('.py')]
for module in modules:
log.info('loading module %s' % module)
try:
imported = __import__(module)
pass
except ImportError as e:
log.error('module could not be imported: %s' % e)
continue
clsobj = getattr(imported, module, None)
modobj = clsobj(self.parent)
# add the module to the list
self.modlist.append(modobj)
# sort modules
self.modlist.sort(key=operator.attrgetter('priority'))
for module in self.modlist:
self.choices.append(module.name)
return (self.modlist, self.choices)
class FuelSetup(object):
def __init__(self):
self.footer = None
self.frame = None
self.screen = None
self.defaultsettingsfile = os.path.join(os.path.dirname(__file__),
"settings.yaml")
self.settingsfile = "/etc/fuel/astute.yaml"
self.managediface = "eth0"
#Set to true to move all settings to end
self.globalsave = True
self.version = self.getVersion("/etc/fuel/version.yaml")
self.main()
self.choices = []
def menu(self, title, choices):
body = [urwid.Text(title), urwid.Divider()]
for c in choices:
button = urwid.Button(c)
urwid.connect_signal(button, 'click', self.menu_chosen, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleListWalker(body))
#return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def menu_chosen(self, button, choice):
size = self.screen.get_cols_rows()
self.screen.draw_screen(size, self.frame.render(size))
for item in self.menuitems.body.contents:
try:
if item.original_widget and \
item.original_widget.get_label() == choice:
item.set_attr_map({None: 'header'})
else:
item.set_attr_map({None: None})
except Exception as e:
log.info("%s" % item)
log.error("%s" % e)
self.setChildScreen(name=choice)
def setChildScreen(self, name=None):
if name is None:
self.child = self.children[0]
else:
self.child = self.children[int(self.choices.index(name))]
if not self.child.screen:
self.child.screen = self.child.screenUI()
self.childpage = self.child.screen
self.childfill = urwid.Filler(self.childpage, 'top', 40)
self.childbox = urwid.BoxAdapter(self.childfill, 40)
self.cols = urwid.Columns(
[
('fixed', 20, urwid.Pile([
urwid.AttrMap(self.menubox, 'bright'),
urwid.Divider(" ")])),
('weight', 3, urwid.Pile([
urwid.Divider(" "),
self.childbox,
urwid.Divider(" ")]))
], 1)
self.child.refresh()
self.listwalker[:] = [self.cols]
def refreshScreen(self):
size = self.screen.get_cols_rows()
self.screen.draw_screen(size, self.frame.render(size))
def refreshChildScreen(self, name):
child = self.children[int(self.choices.index(name))]
#Refresh child listwalker
child.listwalker[:] = child.listbox_content
#reassign childpage top level objects
self.childpage = urwid.ListBox(child.listwalker)
self.childfill = urwid.Filler(self.childpage, 'middle', 22)
self.childbox = urwid.BoxAdapter(self.childfill, 22)
self.cols = urwid.Columns(
[
('fixed', 20, urwid.Pile([
urwid.AttrMap(self.menubox, 'bright'),
urwid.Divider(" ")])),
('weight', 3, urwid.Pile([
urwid.Divider(" "),
self.childbox,
urwid.Divider(" ")]))
], 1)
#Refresh top level listwalker
#self.listwalker[:] = [self.cols]
def getVersion(self, versionfile):
try:
versiondata = Settings().read(versionfile)
return versiondata['VERSION']['release']
except (IOError, KeyError):
log.error("Unable to set Fuel version from %s" % versionfile)
return ""
def main(self):
#Disable kernel print messages. They make our UI ugly
noout = open('/dev/null', 'w')
subprocess.call(["sysctl", "-w", "kernel.printk=4 1 1 7"],
stdout=noout, stderr=noout)
text_header = (u"Fuel %s setup "
u"Use Up/Down/Left/Right to navigate. F8 exits."
% self.version)
text_footer = (u"Status messages go here.")
#Top and bottom lines of frame
self.header = urwid.AttrWrap(urwid.Text(text_header), 'header')
self.footer = urwid.AttrWrap(urwid.Text(text_footer), 'footer')
#Prepare submodules
loader = Loader(self)
moduledir = "%s/modules" % (os.path.dirname(__file__))
self.children, self.choices = loader.load_modules(module_dir=moduledir)
if len(self.children) == 0:
import sys
sys.exit(1)
#Build list of choices excluding visible
self.visiblechoices = []
for child, choice in zip(self.children, self.choices):
if child.visible:
self.visiblechoices.append(choice)
self.menuitems = self.menu(u'Menu', self.visiblechoices)
menufill = urwid.Filler(self.menuitems, 'top', 40)
self.menubox = urwid.BoxAdapter(menufill, 40)
self.child = self.children[0]
self.childpage = self.child.screenUI()
self.childfill = urwid.Filler(self.childpage, 'top', 22)
self.childbox = urwid.BoxAdapter(self.childfill, 22)
self.cols = urwid.Columns(
[
('fixed', 20, urwid.Pile([
urwid.AttrMap(self.menubox, 'bright'),
urwid.Divider(" ")])),
('weight', 3, urwid.Pile([
urwid.Divider(" "),
self.childbox,
urwid.Divider(" ")]))
], 1)
self.listwalker = urwid.SimpleListWalker([self.cols])
#self.listwalker = urwid.TreeWalker([self.cols])
self.listbox = urwid.ListBox(self.listwalker)
#listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content))
self.frame = urwid.Frame(urwid.AttrWrap(self.listbox, 'body'),
header=self.header, footer=self.footer)
palette = \
[
('body', 'black', 'light gray', 'standout'),
('reverse', 'light gray', 'black'),
('header', 'white', 'dark red', 'bold'),
('important', 'dark blue', 'light gray',
('standout', 'underline')),
('editfc', 'white', 'dark blue', 'bold'),
('editbx', 'light gray', 'dark blue'),
('editcp', 'black', 'light gray', 'standout'),
('bright', 'dark gray', 'light gray', ('bold', 'standout')),
('buttn', 'black', 'dark cyan'),
('buttnf', 'white', 'dark blue', 'bold'),
('light gray', 'white', 'light gray', 'bold'),
('red', 'dark red', 'light gray', 'bold'),
('black', 'black', 'black', 'bold'),
]
# use appropriate Screen class
if urwid.web_display.is_web_request():
self.screen = urwid.web_display.Screen()
else:
self.screen = urwid.raw_display.Screen()
def unhandled(key):
if key == 'f8':
raise urwid.ExitMainLoop()
if key == 'shift tab':
self.child.walker.tab_prev()
if key == 'tab':
self.child.walker.tab_next()
self.mainloop = urwid.MainLoop(self.frame, palette, self.screen,
unhandled_input=unhandled)
#Initialize each module completely before any events are handled
for child in reversed(self.children):
self.setChildScreen(name=child.name)
signal.signal(signal.SIGUSR1, self.handle_sigusr1)
dialog.display_dialog(
self.child,
widget.TextLabel("It is highly recommended to change default "
"admin password."),
"WARNING!")
self.mainloop.run()
def exit_program(self, button):
#return kernel logging to normal
noout = open('/dev/null', 'w')
subprocess.call(["sysctl", "-w", "kernel.printk=7 4 1 7"],
stdout=noout, stderr=noout)
#Fix /etc/hosts before quitting
dnsobj = self.children[int(self.choices.index("DNS & Hostname"))]
dnsobj.fixEtcHosts()
raise urwid.ExitMainLoop()
def handle_sigusr1(self, signum, stack):
log.info("Received signal: %s" % signum)
try:
savetimeout = 60
success, modulename = timeout.run_with_timeout(
self.global_save, timeout=savetimeout,
default=(False, "timeout"))
if success:
log.info("Save successful!")
else:
log.error("Save failed on module %s" % modulename)
except (KeyboardInterrupt, timeout.TimeoutError):
log.exception("Save on signal timed out. Save not complete.")
except Exception:
log.exception("Save failed for unknown reason:")
self.exit_program(None)
def global_save(self):
#Runs save function for every module
for module, modulename in zip(self.children, self.choices):
#Run invisible modules. They may not have screen methods
if not module.visible:
try:
module.apply(None)
except Exception as e:
log.error("Unable to save module %s: %s" % (modulename, e))
continue
else:
try:
log.info("Checking and applying module: %s"
% modulename)
self.footer.set_text("Checking and applying module: %s"
% modulename)
self.refreshScreen()
module.refresh()
if module.apply(None):
log.info("Saving module: %s" % modulename)
else:
return False, modulename
except AttributeError as e:
log.debug("Module %s does not have save function: %s"
% (modulename, e))
return True, None
def setup():
urwid.web_display.set_preferences("Fuel Setup")
# try to handle short web requests quickly
if urwid.web_display.handle_short_request():
return
FuelSetup()
def save_only(iface, settingsfile='/etc/fuel/astute.yaml'):
import common.network as network
from common import pwgen
import netifaces
#Calculate and set Static/DHCP pool fields
#Max IPs = net size - 2 (master node + bcast)
try:
ip = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
netmask = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['netmask']
mac = netifaces.ifaddresses(iface)[netifaces.AF_LINK][0]['addr']
except Exception:
print("Interface %s is missing either IP address or netmask"
% (iface))
sys.exit(1)
net_ip_list = network.getNetwork(ip, netmask)
try:
dhcp_pool = net_ip_list[1:]
dynamic_start = str(dhcp_pool[0])
dynamic_end = str(dhcp_pool[-1])
except Exception:
print("Unable to define DHCP pools")
sys.exit(1)
try:
hostname, sep, domain = os.uname()[1].partition('.')
except Exception:
print("Unable to calculate hostname and domain")
sys.exit(1)
try:
dhcptimeout = 5
default = []
with timeout.run_with_timeout(dhcp_checker.utils.IfaceState, [iface],
timeout=dhcptimeout) as iface:
dhcp_server_data = timeout.run_with_timeout(
dhcp_checker.api.check_dhcp_on_eth,
[iface, dhcptimeout], timeout=dhcptimeout,
default=default)
except (KeyboardInterrupt, timeout.TimeoutError):
log.debug("DHCP scan timed out")
log.warning(traceback.format_exc())
dhcp_server_data = default
num_dhcp = len(dhcp_server_data)
if num_dhcp == 0:
log.debug("No DHCP servers found")
else:
#Problem exists, but permit user to continue
log.error("%s foreign DHCP server(s) found: %s" %
(num_dhcp, dhcp_server_data))
print("ERROR: %s foreign DHCP server(s) found: %s" %
(num_dhcp, dhcp_server_data))
if network.duplicateIPExists(ip, iface):
log.error("Duplicate host found with IP {0}".format(ip))
print("ERROR: Duplicate host found with IP {0}".format(ip))
defaultsettingsfile = os.path.join(os.path.dirname(__file__),
"settings.yaml")
newsettings = Settings().read(settingsfile)
settings = \
{
"ADMIN_NETWORK/interface": iface,
"ADMIN_NETWORK/ipaddress": ip,
"ADMIN_NETWORK/netmask": netmask,
"ADMIN_NETWORK/mac": mac,
"ADMIN_NETWORK/dhcp_pool_start": dynamic_start,
"ADMIN_NETWORK/dhcp_pool_end": dynamic_end,
"ADMIN_NETWORK/dhcp_gateway": ip,
"HOSTNAME": hostname,
"DNS_DOMAIN": domain,
"DNS_SEARCH": domain,
"astute/user": "naily",
"astute/password": pwgen.password(),
"cobbler/user": "cobbler",
"cobbler/password": pwgen.password(),
"keystone/admin_token": pwgen.password(),
"keystone/ostf_user": "ostf",
"keystone/ostf_password": pwgen.password(),
"keystone/nailgun_user": "nailgun",
"keystone/nailgun_password": pwgen.password(),
"keystone/monitord_user": "monitord",
"keystone/monitord_password": pwgen.password(),
"mcollective/user": "mcollective",
"mcollective/password": pwgen.password(),
"postgres/keystone_dbname": "keystone",
"postgres/keystone_user": "keystone",
"postgres/keystone_password": pwgen.password(),
"postgres/nailgun_dbname": "nailgun",
"postgres/nailgun_user": "nailgun",
"postgres/nailgun_password": pwgen.password(),
"postgres/ostf_dbname": "ostf",
"postgres/ostf_user": "ostf",
"postgres/ostf_password": pwgen.password(),
"FUEL_ACCESS/user": "admin",
"FUEL_ACCESS/password": "admin",
}
for setting in settings.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings.keys():
newsettings[part1] = {}
#Keep old values for passwords if already set
if "password" in setting:
newsettings[part1].setdefault(part2, settings[setting])
else:
newsettings[part1][part2] = settings[setting]
else:
if "password" in setting:
newsettings.setdefault(setting, settings[setting])
else:
newsettings[setting] = settings[setting]
#Write astute.yaml
Settings().write(newsettings, defaultsfile=defaultsettingsfile,
outfn=settingsfile)
def main(*args, **kwargs):
if urwid.VERSION < (1, 1, 0):
print("This program requires urwid 1.1.0 or greater.")
parser = OptionParser()
parser.add_option("-s", "--save-only", dest="save_only",
action="store_true",
help="Save default values and exit.")
parser.add_option("-i", "--iface", dest="iface", metavar="IFACE",
default="eth0", help="Set IFACE as primary.")
options, args = parser.parse_args()
if options.save_only:
save_only(options.iface)
else:
setup()
if '__main__' == __name__ or urwid.web_display.is_web_request():
setup()

View File

@ -1,281 +0,0 @@
#!/usr/bin/env python
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.settings import Settings
import logging
import url_access_checker.api as urlck
import url_access_checker.errors as url_errors
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.mirrors')
blank = urwid.Divider()
VERSION_YAML_FILE = '/etc/nailgun/version.yaml'
FUEL_BOOTSTRAP_IMAGE_CONF = '/etc/fuel-bootstrap-image.conf'
BOOTSTRAP_FLAVOR_KEY = 'BOOTSTRAP/flavor'
MOS_REPO_DFLT = 'http://mirror.fuel-infra.org/mos-repos/ubuntu/{mos_version}'
class bootstrapimg(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Bootstrap Image"
self.priority = 55
self.visible = True
self.deployment = "pre"
self.parent = parent
self.distro = 'ubuntu'
self._distro_release = None
self._mos_version = None
self._bootstrap_flavor = None
# UI Text
self.header_content = ["Bootstrap image configuration"]
fields = (
'flavor',
'MIRROR_DISTRO',
'MIRROR_MOS',
'HTTP_PROXY',
'EXTRA_DEB_REPOS')
self.fields = ['BOOTSTRAP/{0}'.format(var) for var in fields]
# TODO(asheplyakov):
# switch to the new MOS APT repo structure when it's ready
mos_repo_dflt = MOS_REPO_DFLT.format(mos_version=self.mos_version)
self.defaults = {
BOOTSTRAP_FLAVOR_KEY: {
"label": "Flavor",
"tooltip": "",
"value": "radio",
"choices": ["CentOS", "Ubuntu (EXPERIMENTAL)"]},
"BOOTSTRAP/MIRROR_DISTRO": {
"label": "Ubuntu mirror",
"tooltip": "Ubuntu APT repo URL",
"value": "http://archive.ubuntu.com/ubuntu"},
"BOOTSTRAP/MIRROR_MOS": {
"label": "MOS mirror",
"tooltip": ("MOS APT repo URL (can use file:// protocol, will"
"use local mirror in such case"),
"value": mos_repo_dflt},
"BOOTSTRAP/HTTP_PROXY": {
"label": "HTTP proxy",
"tooltip": "Use this proxy when building the bootstrap image",
"value": ""},
"BOOTSTRAP/EXTRA_DEB_REPOS": {
"label": "Extra APT repositories",
"tooltip": "Additional repositories for bootstrap image",
"value": ""}
}
self.oldsettings = self.load()
self.screen = None
def _read_version_info(self):
settings = Settings()
dat = settings.read(VERSION_YAML_FILE)
version_info = dat['VERSION']
self._mos_version = version_info['release']
self._distro_release = version_info.get('ubuntu_release', 'trusty')
@property
def mos_version(self):
if not self._mos_version:
self._read_version_info()
return self._mos_version
@property
def distro_release(self):
if not self._distro_release:
self._read_version_info()
return self._distro_release
@property
def responses(self):
ret = dict()
for index, fieldname in enumerate(self.fields):
if fieldname == 'blank':
pass
elif fieldname == BOOTSTRAP_FLAVOR_KEY:
rb_group = self.edits[index].rb_group
flavor = 'centos' if rb_group[0].state else 'ubuntu'
ret[fieldname] = flavor
else:
ret[fieldname] = self.edits[index].get_edit_text()
return ret
def check(self, args):
"""Validate that all fields have valid values through sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
responses = self.responses
errors = []
if responses.get(BOOTSTRAP_FLAVOR_KEY) == 'ubuntu':
errors.extend(self.check_apt_repos(responses))
if errors:
self.parent.footer.set_text("Error: %s" % (errors[0]))
log.error("Errors: %s", errors)
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def check_apt_repos(self, responses):
errors = []
# APT repo URL must not be empty
distro_repo_base = responses['BOOTSTRAP/MIRROR_DISTRO'].strip()
mos_repo_base = responses['BOOTSTRAP/MIRROR_MOS'].strip()
http_proxy = responses['BOOTSTRAP/HTTP_PROXY'].strip()
if len(distro_repo_base) == 0:
errors.append("Ubuntu mirror URL must not be empty.")
if not self.checkDistroRepo(distro_repo_base, http_proxy):
errors.append("Ubuntu repository is not accessible.")
if len(mos_repo_base) == 0:
errors.append("MOS repo URL must not be empty.")
if not self.checkMOSRepo(mos_repo_base, http_proxy):
errors.append("MOS repository is not accessible.")
return errors
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
return False
with open(FUEL_BOOTSTRAP_IMAGE_CONF, "w") as fbiconf:
for var in self.fields:
scope, name = var.split('/')
fbiconf.write('{0}="{1}"\n'.format(name, responses.get(var)))
fbiconf.write('MOS_VERSION="{0}"'.format(self.mos_version))
self.save(responses)
return True
def cancel(self, button):
ModuleHelper.cancel(self, button)
def _ui_set_bootstrap_flavor(self):
rb_index = self.fields.index(BOOTSTRAP_FLAVOR_KEY)
is_ubuntu = self._bootstrap_flavor is not None and \
'ubuntu' in self._bootstrap_flavor
try:
rb_group = self.edits[rb_index].rb_group
rb_group[0].set_state(not is_ubuntu)
rb_group[1].set_state(is_ubuntu)
except AttributeError:
# the UI hasn't been initalized yet
pass
def _set_bootstrap_flavor(self, flavor):
is_ubuntu = flavor is not None and 'ubuntu' in flavor.lower()
self._bootstrap_flavor = 'ubuntu' if is_ubuntu else 'centos'
self._ui_set_bootstrap_flavor()
def load(self):
# Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
for setting in self.defaults:
try:
if BOOTSTRAP_FLAVOR_KEY == setting:
section, key = BOOTSTRAP_FLAVOR_KEY.split('/')
flavor = oldsettings[section][key]
self._set_bootstrap_flavor(flavor)
elif "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
except KeyError:
log.warning("no setting named %s found.", setting)
except Exception as e:
log.warning("unexpected error: %s", e.message)
return oldsettings
def save(self, responses):
# Generic settings start
newsettings = dict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
# We may not touch all settings, so copy oldsettings first
newsettings[part1] = self.oldsettings[part1]
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
# Generic settings end
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
# Set oldsettings to reflect new settings
self.oldsettings = newsettings
# Update self.defaults
for index, fieldname in enumerate(self.fields):
if fieldname != "blank":
log.info("resetting %s", fieldname)
if fieldname not in self.defaults.keys():
log.error("no such field: %s, valid are %s",
fieldname, ' '.join(self.defaults.keys()))
continue
if fieldname not in newsettings.keys():
log.error("newsettings: no such field: %s, valid are %s",
fieldname, ' '.join(newsettings.keys()))
continue
self.defaults[fieldname]['value'] = newsettings[fieldname]
def check_url(self, url, http_proxy):
try:
return urlck.check_urls([url], proxies={'http': http_proxy})
except url_errors.UrlNotAvailable:
return False
def checkDistroRepo(self, base_url, http_proxy):
release_url = '{base_url}/dists/{distro_release}/Release'.format(
base_url=base_url, distro_release=self.distro_release)
available = self.check_url(release_url, http_proxy)
# TODO(asheplyakov):
# check if it's possible to debootstrap with this repo
return available
def checkMOSRepo(self, base_url, http_proxy):
# deb {repo_base_url}/mos/ubuntu mos{mos_version} main
codename = 'mos{0}'.format(self.mos_version)
release_url = '{base_url}/dists/{codename}/Release'.format(
base_url=base_url, codename=codename)
available = self.check_url(release_url, http_proxy)
return available
def radioSelect(self, current, state, user_data=None):
pass
def refresh(self):
pass
def screenUI(self):
screen = ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)
# set the radiobutton state (ModuleHelper handles only yes/no choice)
self._ui_set_bootstrap_flavor()
return screen

View File

@ -1,437 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import dhcp_checker.api
import dhcp_checker.utils
from fuelmenu.common import dialog
from fuelmenu.common.errors import BadIPException
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common import network
from fuelmenu.common import timeout
import fuelmenu.common.urwidwrapper as widget
from fuelmenu.settings import Settings
import logging
import netaddr
import traceback
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.pxe_setup')
blank = urwid.Divider()
class cobblerconf(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "PXE Setup"
self.priority = 20
self.visible = True
self.netsettings = dict()
self.parent = parent
self.deployment = "pre"
self.getNetwork()
self.gateway = self.get_default_gateway_linux()
self.activeiface = sorted(self.netsettings.keys())[0]
self.parent.managediface = self.activeiface
#UI text
text1 = "Settings for PXE booting of slave nodes."
text2 = "Select the interface where PXE will run:"
#Placeholder for network settings text
self.net_choices = widget.ChoicesGroup(sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelect)
self.net_text1 = widget.TextLabel("")
self.net_text2 = widget.TextLabel("")
self.net_text3 = widget.TextLabel("")
self.net_text4 = widget.TextLabel("")
self.header_content = [text1, text2, self.net_choices, self.net_text1,
self.net_text2, self.net_text3, self.net_text4]
self.fields = ["dynamic_label", "ADMIN_NETWORK/dhcp_pool_start",
"ADMIN_NETWORK/dhcp_pool_end",
"ADMIN_NETWORK/dhcp_gateway"]
self.defaults = \
{
"ADMIN_NETWORK/dhcp_pool_start": {"label": "DHCP Pool Start",
"tooltip": "Used for \
defining IPs for hosts and instance public addresses",
"value": "10.0.0.3"},
"ADMIN_NETWORK/dhcp_pool_end": {"label": "DHCP Pool End",
"tooltip": "Used for defining \
IPs for hosts and instance public addresses",
"value": "10.0.0.254"},
"ADMIN_NETWORK/dhcp_gateway": {"label": "DHCP Gateway",
"tooltip": "Default gateway \
to advertise via DHCP to nodes",
"value": "10.0.0.2"},
"dynamic_label": {"label": "DHCP pool for node discovery:",
"tooltip": "",
"value": "label"},
}
self.extdhcp = True
self.oldsettings = self.load()
self.screen = None
def check(self, args):
"""Validates all fields have valid values and some sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
#Refresh networking to make sure IP matches
self.getNetwork()
#Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname != "blank" and "label" not in fieldname:
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
#Set internal_{ipaddress,netmask,interface}
responses["ADMIN_NETWORK/interface"] = self.activeiface
responses["ADMIN_NETWORK/netmask"] = self.netsettings[
self.activeiface]["netmask"]
responses["ADMIN_NETWORK/mac"] = self.netsettings[
self.activeiface]["mac"]
responses["ADMIN_NETWORK/ipaddress"] = self.netsettings[
self.activeiface]["addr"]
#ensure management interface is valid
if responses["ADMIN_NETWORK/interface"] not in self.netsettings.keys():
errors.append("Management interface not valid")
else:
self.parent.footer.set_text("Scanning for DHCP servers. \
Please wait...")
self.parent.refreshScreen()
###Start DHCP check on this interface
#dhcp_server_data=[{'server_id': '192.168.200.2', 'iface': 'eth2',
# 'yiaddr': '192.168.200.15', 'mac':
# '52:54:00:12:35:02', 'server_ip': '192.168.200.2',
# 'dport': 67, 'message': 'offer',
# 'gateway': '0.0.0.0'}]
try:
dhcptimeout = 5
default = []
with timeout.run_with_timeout(dhcp_checker.utils.IfaceState,
[self.activeiface],
timeout=dhcptimeout) as iface:
dhcp_server_data = timeout.run_with_timeout(
dhcp_checker.api.check_dhcp_on_eth,
[iface, dhcptimeout], timeout=dhcptimeout,
default=default)
except (KeyboardInterrupt, timeout.TimeoutError):
log.debug("DHCP scan timed out")
log.warning(traceback.format_exc())
dhcp_server_data = default
num_dhcp = len(dhcp_server_data)
if num_dhcp == 0:
log.debug("No DHCP servers found")
else:
#Problem exists, but permit user to continue
log.error("%s foreign DHCP server(s) found: %s" %
(num_dhcp, dhcp_server_data))
#Build dialog elements
dhcp_info = []
dhcp_info.append(urwid.Padding(
urwid.Text(("header", "!!! WARNING !!!")),
"center"))
dhcp_info.append(widget.TextLabel("You have selected an \
interface that contains one or more DHCP servers. This will impact \
provisioning. You should disable these DHCP servers before you continue, or \
else deployment will likely fail."))
dhcp_info.append(widget.TextLabel(""))
for index, dhcp_server in enumerate(dhcp_server_data):
dhcp_info.append(widget.TextLabel("DHCP Server #%s:" %
(index + 1)))
dhcp_info.append(widget.TextLabel("IP address: %-10s" %
dhcp_server['server_ip']))
dhcp_info.append(widget.TextLabel("MAC address: %-10s" %
dhcp_server['mac']))
dhcp_info.append(widget.TextLabel(""))
dialog.display_dialog(self, urwid.Pile(dhcp_info),
"DHCP Servers Found on %s"
% self.activeiface)
###Ensure pool start and end are on the same subnet as mgmt_if
#Ensure mgmt_if has an IP first
if len(self.netsettings[responses[
"ADMIN_NETWORK/interface"]]["addr"]) == 0:
errors.append("Go to Interfaces to configure management \
interface first.")
else:
#Ensure ADMIN_NETWORK/interface is not running DHCP
if self.netsettings[responses[
"ADMIN_NETWORK/interface"]]["bootproto"] == "dhcp":
errors.append("%s is running DHCP. Change it to static "
"first." % self.activeiface)
#Ensure DHCP Pool Start and DHCP Pool are valid IPs
try:
if netaddr.valid_ipv4(responses[
"ADMIN_NETWORK/dhcp_pool_start"]):
dhcp_start = netaddr.IPAddress(
responses["ADMIN_NETWORK/dhcp_pool_start"])
if not dhcp_start:
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except Exception:
errors.append("Invalid IP address for DHCP Pool Start")
try:
if netaddr.valid_ipv4(responses[
"ADMIN_NETWORK/dhcp_gateway"]):
dhcp_gateway = netaddr.IPAddress(
responses["ADMIN_NETWORK/dhcp_gateway"])
if not dhcp_gateway:
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except Exception:
errors.append("Invalid IP address for DHCP Gateway")
try:
if netaddr.valid_ipv4(responses[
"ADMIN_NETWORK/dhcp_pool_end"]):
dhcp_end = netaddr.IPAddress(
responses["ADMIN_NETWORK/dhcp_pool_end"])
if not dhcp_end:
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except Exception:
errors.append("Invalid IP address for DHCP Pool end")
#Ensure pool start and end are in the same subnet of each other
netmask = self.netsettings[responses[
"ADMIN_NETWORK/interface"
]]["netmask"]
if not network.inSameSubnet(
responses["ADMIN_NETWORK/dhcp_pool_start"],
responses["ADMIN_NETWORK/dhcp_pool_end"], netmask):
errors.append("DHCP Pool start and end are not in the "
"same subnet.")
#Ensure pool start and end are in the right netmask
mgmt_if_ipaddr = self.netsettings[responses[
"ADMIN_NETWORK/interface"]]["addr"]
if network.inSameSubnet(responses[
"ADMIN_NETWORK/dhcp_pool_start"],
mgmt_if_ipaddr, netmask) is False:
errors.append("DHCP Pool start does not match management"
" network.")
if network.inSameSubnet(responses[
"ADMIN_NETWORK/dhcp_pool_end"],
mgmt_if_ipaddr, netmask) is False:
errors.append("DHCP Pool end does not match management "
"network.")
if network.inSameSubnet(responses[
"ADMIN_NETWORK/dhcp_gateway"],
mgmt_if_ipaddr, netmask) is False:
errors.append("DHCP Gateway does not match management "
"network.")
self.parent.footer.set_text("Scanning for duplicate IP address"
"es. Please wait...")
# Bind arping to mgmt_if_ipaddr if it assigned
assigned_ips = [v.get('addr') for v in
self.netsettings.itervalues()]
arping_bind = mgmt_if_ipaddr in assigned_ips
if network.duplicateIPExists(mgmt_if_ipaddr, self.activeiface,
arping_bind):
errors.append("Duplicate host found with IP {0}.".format(
mgmt_if_ipaddr))
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
return False
#Always save even if "post"
self.save(responses)
return True
def cancel(self, button):
ModuleHelper.cancel(self, button)
self.setNetworkDetails()
def load(self):
#Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings.copy()
oldsettings.update(Settings().read(self.parent.settingsfile))
log.debug("Old settings %s" % oldsettings)
for setting in self.defaults.keys():
if "label" in setting:
continue
elif "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
if oldsettings["ADMIN_NETWORK"]["interface"] \
in self.netsettings.keys():
self.activeiface = oldsettings["ADMIN_NETWORK"]["interface"]
return oldsettings
def save(self, responses):
## Generic settings start ##
newsettings = ModuleHelper.save(self, responses)
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
newsettings[part1] = self.oldsettings[part1]
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
## Generic settings end ##
## Need to calculate and netmask
newsettings['ADMIN_NETWORK']['netmask'] = \
self.netsettings[newsettings['ADMIN_NETWORK']['interface']][
"netmask"]
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update self.defaults
for index, fieldname in enumerate(self.fields):
if fieldname != "blank" and "label" not in fieldname:
self.defaults[fieldname]['value'] = responses[fieldname]
self.parent.footer.set_text("Changes saved successfully.")
def getNetwork(self):
ModuleHelper.getNetwork(self)
def getDHCP(self, iface):
return ModuleHelper.getDHCP(iface)
def get_default_gateway_linux(self):
return ModuleHelper.get_default_gateway_linux()
def radioSelect(self, current, state, user_data=None):
"""Update network details and display information."""
### Urwid returns the previously selected radio button.
### The previous object has True state, which is wrong.
### Somewhere in rb group a RadioButton is set to True.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.activeiface = rb.base_widget.get_label()
self.parent.managediface = self.activeiface
break
self.gateway = self.get_default_gateway_linux()
self.getNetwork()
self.setNetworkDetails()
return
def setNetworkDetails(self):
self.net_text1.set_text("Interface: %-13s Link: %s" % (
self.activeiface, self.netsettings[self.activeiface]['link'].
upper()))
self.net_text2.set_text("IP: %-15s MAC: %s" % (self.netsettings[
self.activeiface]['addr'],
self.netsettings[self.activeiface]['mac']))
self.net_text3.set_text("Netmask: %-15s Gateway: %s" % (
self.netsettings[self.activeiface]['netmask'],
self.gateway))
log.debug("bootproto for %s: %s" % (self.netsettings[self.activeiface],
self.netsettings[self.activeiface]['bootproto']))
if self.netsettings[self.activeiface]['link'].upper() == "UP":
if self.netsettings[self.activeiface]['bootproto'] == "dhcp":
self.net_text4.set_text("WARNING: Cannot use interface running"
" DHCP.\nReconfigure as static in "
"Network Setup screen.")
else:
self.net_text4.set_text("")
else:
self.net_text4.set_text("WARNING: This interface is DOWN. "
"Configure it first.")
#If DHCP pool start and matches activeiface network, don't update
#This means if you change your pool values, go to another page, then
#go back, it will not reset your changes. But what is more likely is
#you will change the network settings for interface eth0 and then come
#back to this page to update your DHCP settings. If the inSameSubnet
#test fails, just recalculate and set new values.
for index, key in enumerate(self.fields):
if key == "ADMIN_NETWORK/dhcp_pool_start":
dhcp_start = self.edits[index].get_edit_text()
break
if network.inSameSubnet(dhcp_start,
self.netsettings[self.activeiface]['addr'],
self.netsettings[self.activeiface]['netmask']):
log.debug("Existing network settings exist. Not changing.")
return
else:
log.debug("Existing network settings missing or invalid. "
"Updating...")
#Calculate and set Static/DHCP pool fields
#Max IPs = net size - 2 (master node + bcast)
#Add gateway so we exclude it
net_ip_list = network.getNetwork(
self.netsettings[self.activeiface]['addr'],
self.netsettings[self.activeiface]['netmask'],
self.gateway)
try:
dhcp_pool = net_ip_list[1:]
dynamic_start = str(dhcp_pool[0])
dynamic_end = str(dhcp_pool[-1])
if self.net_text4.get_text() == "":
self.net_text4.set_text("This network configuration can "
"support %s nodes." % len(dhcp_pool))
except Exception:
#We don't have valid values, so mark all fields empty
dynamic_start = ""
dynamic_end = ""
for index, key in enumerate(self.fields):
if key == "ADMIN_NETWORK/dhcp_pool_start":
self.edits[index].set_edit_text(dynamic_start)
elif key == "ADMIN_NETWORK/dhcp_pool_end":
self.edits[index].set_edit_text(dynamic_end)
elif key == "ADMIN_NETWORK/dhcp_gateway":
self.edits[index].set_edit_text(self.netsettings[
self.activeiface]['addr'])
def refresh(self):
self.getNetwork()
self.setNetworkDetails()
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,393 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common import dialog
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common import replace
import fuelmenu.common.urwidwrapper as widget
from fuelmenu.settings import Settings
import logging
import netaddr
import os
import re
import socket
import subprocess
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.mirrors')
blank = urwid.Divider()
class dnsandhostname(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "DNS & Hostname"
self.priority = 50
self.visible = True
self.netsettings = dict()
self.deployment = "pre"
self.getNetwork()
self.gateway = self.get_default_gateway_linux()
self.extdhcp = True
self.parent = parent
#UI Text
self.header_content = ["DNS and hostname setup", "Note: Leave "
"External DNS blank if you do not have "
"Internet access."]
self.fields = ["HOSTNAME", "DNS_DOMAIN", "DNS_SEARCH", "DNS_UPSTREAM",
"blank", "TEST_DNS"]
hostname, sep, domain = os.uname()[1].partition('.')
self.defaults = \
{
"HOSTNAME": {"label": "Hostname",
"tooltip": "Hostname to use for Fuel master node",
"value": hostname},
"DNS_UPSTREAM": {"label": "External DNS",
"tooltip": "DNS server(s) (comma separated) \
to handle DNS requests (example 8.8.8.8,8.8.4.4)",
"value": "8.8.8.8"},
"DNS_DOMAIN": {"label": "Domain",
"tooltip": "Domain suffix to user for all \
nodes in your cluster",
"value": domain},
"DNS_SEARCH": {"label": "Search Domain",
"tooltip": "Domains to search when looking up \
DNS (space separated)",
"value": domain},
"TEST_DNS": {"label": "Hostname to test DNS:",
"value": "www.google.com",
"tooltip": "DNS record to resolve to see if DNS \
is accessible"}
}
self.oldsettings = self.load()
self.screen = None
self.fixEtcHosts()
def fixEtcHosts(self):
#replace ip for env variable HOSTNAME in /etc/hosts
if self.netsettings[self.parent.managediface]["addr"] != "":
managediface_ip = self.netsettings[
self.parent.managediface]["addr"]
else:
managediface_ip = "127.0.0.1"
found = False
with open("/etc/hosts") as fh:
for line in fh:
if re.match("%s.*%s" % (managediface_ip,
socket.gethostname()), line):
found = True
break
if not found:
expr = ".*%s.*" % socket.gethostname()
replace.replaceInFile("/etc/hosts", expr, "%s %s %s" % (
managediface_ip,
socket.gethostname(),
socket.gethostname().split('.')[0]))
def check(self, args):
"""Validate that all fields have valid values through sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
#Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname == "blank":
pass
else:
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
#hostname must be under 60 chars
if len(responses["HOSTNAME"]) >= 60:
errors.append("Hostname must be under 60 chars.")
#hostname must not be empty
if len(responses["HOSTNAME"]) == 0:
errors.append("Hostname must not be empty.")
#hostname needs to have valid chars
if re.search('[^a-z0-9-]', responses["HOSTNAME"]):
errors.append(
"Hostname must contain only alphanumeric and hyphen.")
#domain must be under 180 chars
if len(responses["DNS_DOMAIN"]) >= 180:
errors.append("Domain must be under 180 chars.")
#domain must not be empty
if len(responses["DNS_DOMAIN"]) == 0:
errors.append("Domain must not be empty.")
#domain needs to have valid chars
if re.match('[^a-z0-9-.]', responses["DNS_DOMAIN"]):
errors.append(
"Domain must contain only alphanumeric, period and hyphen.")
#ensure external DNS is valid
if len(responses["DNS_UPSTREAM"]) == 0:
#We will allow empty if user doesn't need external networking
#and present a strongly worded warning
msg = "If you continue without DNS, you may not be able to access"\
+ " external data necessary for installation needed for " \
+ "some OpenStack Releases."
dialog.display_dialog(
self, widget.TextLabel(msg), "Empty DNS Warning")
else:
#external DNS must contain only numbers, periods, and commas
#Needs more serious ip address checking
if re.match('[^0-9.,]', responses["DNS_UPSTREAM"]):
errors.append(
"External DNS must contain only IP addresses and commas.")
admin_ip = self.netsettings[self.parent.managediface]['addr']
if admin_ip in responses["DNS_UPSTREAM"]:
errors.append("Admin interface IP cannot be in upstream "
"nameservers.")
#ensure test DNS name isn't empty
if len(responses["TEST_DNS"]) == 0:
errors.append("Test DNS must not be empty.")
#Validate first IP address
try:
if netaddr.valid_ipv4(responses["DNS_UPSTREAM"].split(",")[0]):
DNS_UPSTREAM = responses["DNS_UPSTREAM"].split(",")[0]
else:
errors.append("Not a valid IP address for External DNS: %s"
% responses["DNS_UPSTREAM"])
#Try to resolve with first address
if not self.checkDNS(DNS_UPSTREAM):
#Warn user that DNS resolution failed, but continue
msg = "Unable to resolve %s.\n\n" % responses['TEST_DNS']\
+ "Possible causes for DNS failure include:\n"\
+ "* Invalid DNS server\n"\
+ "* Invalid gateway\n"\
+ "* Other networking issue\n\n"\
+ "Fuel Setup can save this configuration, but "\
+ "you may want to correct your settings."
dialog.display_dialog(self, widget.TextLabel(msg),
"DNS Failure Warning")
self.parent.refreshScreen()
except Exception:
errors.append("Not a valid IP address for External DNS: %s"
% responses["DNS_UPSTREAM"])
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
return False
self.save(responses)
#Update network details so we write correct IP address
self.getNetwork()
#Apply hostname
expr = 'HOSTNAME=.*'
replace.replaceInFile("/etc/sysconfig/network", expr,
"HOSTNAME=%s.%s"
% (responses["HOSTNAME"],
responses["DNS_DOMAIN"]))
#remove old hostname from /etc/hosts
f = open("/etc/hosts", "r")
lines = f.readlines()
f.close()
with open("/etc/hosts", "w") as etchosts:
for line in lines:
if "localhost" in line:
etchosts.write(line)
elif responses["HOSTNAME"] in line \
or self.oldsettings["HOSTNAME"] \
or self.netsettings[self.parent.managediface]['addr'] \
in line:
continue
else:
etchosts.write(line)
etchosts.close()
#append hostname and ip address to /etc/hosts
with open("/etc/hosts", "a") as etchosts:
if self.netsettings[self.parent.managediface]["addr"] != "":
managediface_ip = self.netsettings[
self.parent.managediface]["addr"]
else:
managediface_ip = "127.0.0.1"
etchosts.write(
"%s %s.%s %s\n" % (managediface_ip, responses["HOSTNAME"],
responses['DNS_DOMAIN'],
responses["HOSTNAME"]))
etchosts.close()
def make_resolv_conf(filename):
with open(filename, 'w') as f:
f.write("search %s\n" % responses['DNS_SEARCH'])
f.write("domain %s\n" % responses['DNS_DOMAIN'])
for upstream_dns in responses['DNS_UPSTREAM'].split(','):
f.write("nameserver %s\n" % upstream_dns)
# Create a temporary resolv.conf so DNS works before the cobbler
# container is up and running.
# TODO(asheplyakov): puppet does a similar thing, perhaps we can
# use the corresponding template instead of duplicating it here.
make_resolv_conf('/etc/resolv.conf')
return True
def cancel(self, button):
ModuleHelper.cancel(self, button)
def load(self):
# Precedence of DNS information:
# Class defaults, fuelmenu default YAML, astute.yaml, uname,
# /etc/resolv.conf
#Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
oldsettings = Settings().read(self.parent.settingsfile)
#Read hostname if it's already set
try:
hostname, sep, domain = os.uname()[1].partition('.')
oldsettings["HOSTNAME"] = hostname
oldsettings["DNS_DOMAIN"] = domain
oldsettings["DNS_SEARCH"] = domain
except Exception:
log.warning("Unable to look up system hostname")
# Parse /etc/resolv.conf if it contains data
search, domain, nameservers = self.getDNS()
if search:
oldsettings["DNS_SEARCH"] = search
if domain:
oldsettings["DNS_DOMAIN"] = domain
if nameservers:
oldsettings["DNS_UPSTREAM"] = nameservers
for setting in self.defaults.keys():
try:
if "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
except Exception:
log.warning("No setting named %s found." % setting)
continue
return oldsettings
def getDNS(self, resolver="/etc/resolv.conf"):
nameservers = []
domain = None
searches = None
try:
with open(resolver, 'r') as f:
for line in f:
line = line.strip()
if line.startswith("search "):
searches = ' '.join(line.split(' ')[1:])
if line.startswith("domain "):
domain = line.split(' ')[1]
if line.startswith("nameserver "):
nameservers.append(line.split(' ')[1])
except EnvironmentError:
log.warn("Unable to open /etc/resolv.conf")
# Always remove admin interface IP from nameserver list
admin_ip = self.netsettings[self.parent.managediface]["addr"]
if admin_ip in nameservers:
try:
nameservers.remove(admin_ip)
except ValueError:
pass
return searches, domain, ",".join(nameservers)
def save(self, responses):
## Generic settings start ##
newsettings = dict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
newsettings[part1] = self.oldsettings[part1]
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
## Generic settings end ##
#log.debug(str(newsettings))
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update self.defaults
for index, fieldname in enumerate(self.fields):
if fieldname != "blank":
self.defaults[fieldname]['value'] = newsettings[fieldname]
def checkDNS(self, server):
#Note: Python's internal resolver caches negative answers.
#Therefore, we should call dig externally to be sure.
noout = open('/dev/null', 'w')
dns_works = subprocess.call(["dig", "+short", "+time=3",
"+retries=1",
self.defaults["TEST_DNS"]['value'],
"@%s" % server], stdout=noout,
stderr=noout)
if dns_works != 0:
return False
else:
return True
def getNetwork(self):
ModuleHelper.getNetwork(self)
def getDHCP(self, iface):
return ModuleHelper.getDHCP(iface)
def get_default_gateway_linux(self):
return ModuleHelper.get_default_gateway_linux()
def radioSelect(self, current, state, user_data=None):
pass
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,169 +0,0 @@
#!/usr/bin/env python
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
try:
from collections import OrderedDict
except Exception:
# python 2.6 or earlier use backport
from ordereddict import OrderedDict
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.settings import Settings
import logging
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.rootpw')
blank = urwid.Divider()
class fueluser(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Fuel User"
self.priority = 1
self.visible = True
self.parent = parent
# UI text
self.header_content = [
"Set Fuel User password.",
"Default user: admin",
"Default password: admin",
"",
"The password should contain upper and lower-case letters, digits"
", and characters like !@#$%^&*()_+."
]
self.fields = ["FUEL_ACCESS/password", "CONFIRM_PASSWORD"]
self.defaults = \
{
"FUEL_ACCESS/password": {"label": "Fuel password",
"tooltip": "ASCII characters only",
"value": ""},
"CONFIRM_PASSWORD": {"label": "Confirm password",
"tooltip": "ASCII characters only",
"value": ""},
}
self.oldsettings = self.load()
self.screen = None
def check(self, args):
"""Validate that all fields have valid values and sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
# Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname != "blank":
responses[fieldname] = self.edits[index].get_edit_text()
# Validate each field
errors = []
# Passwords must match
if responses["FUEL_ACCESS/password"] != responses["CONFIRM_PASSWORD"]:
# Ignore if password is unchanged
if responses["FUEL_ACCESS/password"] != self.defaults[
'FUEL_ACCESS/password']['value']:
errors.append("Passwords do not match.")
# Password must not be empty
if len(responses["FUEL_ACCESS/password"]) == 0:
errors.append("Password must not be empty.")
# Password needs to be in ASCII character set
try:
if responses["FUEL_ACCESS/password"].decode('ascii'):
pass
except UnicodeDecodeError:
errors.append("Password contains non-ASCII characters.")
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
self.parent.footer.set_text("No errors found.")
# Remove confirm from responses so it isn't saved
del responses["CONFIRM_PASSWORD"]
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
for index, fieldname in enumerate(self.fields):
if fieldname == "FUEL_ACCESS/password":
return (self.edits[index].get_edit_text() == "")
return False
self.save(responses)
return True
def save(self, responses):
## Generic settings start ##
newsettings = OrderedDict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
try:
newsettings[part1] = self.oldsettings[part1]
except Exception:
if part1 not in newsettings.keys():
newsettings[part1] = OrderedDict()
log.warning("issues setting newsettings %s " % setting)
log.warning("current newsettings: %s" % newsettings)
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
self.parent.footer.set_text("Changes applied successfully.")
# Reset fields
self.cancel(None)
def load(self):
# Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
oldsettings = Settings().read(self.parent.settingsfile)
for setting in self.defaults.keys():
try:
if "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
except Exception:
log.warning("No setting named %s found." % setting)
continue
return oldsettings
def cancel(self, button):
ModuleHelper.cancel(self, button)
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,435 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import dhcp_checker.api
import dhcp_checker.utils
from fuelmenu.common import dialog
from fuelmenu.common.errors import BadIPException
from fuelmenu.common.errors import NetworkException
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common import network
from fuelmenu.common import puppet
from fuelmenu.common import replace
from fuelmenu.common import timeout
import fuelmenu.common.urwidwrapper as widget
import logging
import netaddr
import re
import socket
import subprocess
import traceback
import urwid
import urwid.raw_display
import urwid.web_display
blank = urwid.Divider()
#Need to define fields in order so it will render correctly
class interfaces(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Network Setup"
self.priority = 5
self.visible = True
self.netsettings = dict()
self.parent = parent
self.screen = None
self.log = logging
self.log.basicConfig(filename='./fuelmenu.log', level=logging.DEBUG)
self.log.info("init Interfaces")
self.getNetwork()
self.gateway = self.get_default_gateway_linux()
self.activeiface = sorted(self.netsettings.keys())[0]
self.extdhcp = True
#UI text
self.net_choices = widget.ChoicesGroup(sorted(self.netsettings.keys()),
default_value=self.activeiface,
fn=self.radioSelectIface)
#Placeholders for network settings text
self.net_text1 = widget.TextLabel("")
self.net_text2 = widget.TextLabel("")
self.net_text3 = widget.TextLabel("")
self.header_content = [self.net_choices, self.net_text1,
self.net_text2, self.net_text3]
self.fields = ["blank", "ifname", "onboot", "bootproto", "ipaddr",
"netmask", "gateway"]
self.defaults = \
{
"ifname": {"label": "Interface name:",
"tooltip": "Interface system identifier",
"value": "locked"},
"onboot": {"label": "Enable interface:",
"tooltip": "",
"value": "radio"},
"bootproto": {"label": "Configuration via DHCP:",
"tooltip": "",
"value": "radio",
"choices": ["Static", "DHCP"]},
"ipaddr": {"label": "IP address:",
"tooltip": "Manual IP address (example \
192.168.1.2)",
"value": ""},
"netmask": {"label": "Netmask:",
"tooltip": "Manual netmask (example \
255.255.255.0)",
"value": "255.255.255.0"},
"gateway": {"label": "Default Gateway:",
"tooltip": "Manual gateway to access Internet \
(example 192.168.1.1)",
"value": ""},
}
def fixEtcHosts(self):
#replace ip for env variable HOSTNAME in /etc/hosts
if self.netsettings[self.parent.managediface]["addr"] != "":
managediface_ip = self.netsettings[self.parent.managediface][
"addr"]
else:
managediface_ip = "127.0.0.1"
found = False
with open("/etc/hosts") as fh:
for line in fh:
if re.match("%s.*%s" % (managediface_ip, socket.gethostname()),
line):
found = True
break
if not found:
expr = ".*%s.*" % socket.gethostname()
replace.replaceInFile("/etc/hosts", expr, "%s %s %s" % (
managediface_ip, socket.gethostname(),
socket.gethostname().split(".")[0]))
def check(self, args):
"""Validate that all fields have valid values and sanity checks."""
#Get field information
responses = dict()
self.parent.footer.set_text("Checking data...")
for index, fieldname in enumerate(self.fields):
if fieldname == "blank" or fieldname == "ifname":
pass
elif fieldname == "bootproto":
rb_group = self.edits[index].rb_group
if rb_group[0].state:
responses["bootproto"] = "none"
else:
responses["bootproto"] = "dhcp"
elif fieldname == "onboot":
rb_group = self.edits[index].rb_group
if rb_group[0].state:
responses["onboot"] = "yes"
else:
responses["onboot"] = "no"
else:
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
if responses["onboot"] == "no":
numactiveifaces = 0
for iface in self.netsettings:
if self.netsettings[iface]['addr'] != "":
numactiveifaces += 1
if numactiveifaces < 2 and \
self.netsettings[self.activeiface]['addr'] != "":
#Block user because puppet l23network fails if all intefaces
#are disabled.
errors.append("Cannot disable all interfaces.")
elif responses["bootproto"] == "dhcp":
self.parent.footer.set_text("Scanning for DHCP servers. "
"Please wait...")
self.parent.refreshScreen()
try:
dhcptimeout = 5
with timeout.run_with_timeout(dhcp_checker.utils.IfaceState,
[self.activeiface],
timeout=dhcptimeout) as iface:
dhcp_server_data = timeout.run_with_timeout(
dhcp_checker.api.check_dhcp_on_eth,
[iface, dhcptimeout], timeout=dhcptimeout)
except (KeyboardInterrupt, timeout.TimeoutError):
self.log.debug("DHCP scan timed out")
self.log.warning(traceback.format_exc())
dhcp_server_data = []
except Exception:
self.log.warning("dhcp_checker failed to check on %s"
% self.activeiface)
dhcp_server_data = []
responses["dhcp_nowait"] = False
if len(dhcp_server_data) < 1:
self.log.debug("No DHCP servers found. Warning user about "
"dhcp_nowait.")
#Build dialog elements
dhcp_info = []
dhcp_info.append(urwid.Padding(
urwid.Text(("header", "!!! WARNING !!!")),
"center"))
dhcp_info.append(
widget.TextLabel(
"Unable to detect DHCP server " +
"on interface %s." % (self.activeiface) +
"\nDHCP will be set up in the background, " +
"but may not receive an IP address. You may " +
"want to check your DHCP connection manually " +
"using the Shell Login menu to the left."))
dialog.display_dialog(self, urwid.Pile(dhcp_info),
"DHCP Servers Found on %s"
% self.activeiface)
self.parent.refreshScreen()
responses["dhcp_nowait"] = True
#Check ipaddr, netmask, gateway only if static
elif responses["bootproto"] == "none":
try:
if netaddr.valid_ipv4(responses["ipaddr"]):
if not netaddr.IPAddress(responses["ipaddr"]):
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except (BadIPException, Exception):
errors.append("Not a valid IP address: %s" %
responses["ipaddr"])
try:
if netaddr.valid_ipv4(responses["netmask"]):
netmask = netaddr.IPAddress(responses["netmask"])
if netmask.is_netmask is False:
raise BadIPException("Not a valid IP address")
else:
raise BadIPException("Not a valid IP address")
except (BadIPException, Exception):
errors.append("Not a valid netmask: %s" % responses["netmask"])
try:
if len(responses["gateway"]) > 0:
#Check if gateway is valid
if netaddr.valid_ipv4(responses["gateway"]) is False:
raise BadIPException("Gateway IP address is not valid")
#Check if gateway is in same subnet
if network.inSameSubnet(responses["ipaddr"],
responses["gateway"],
responses["netmask"]) is False:
raise BadIPException("Gateway IP is not in same "
"subnet as IP address")
except (BadIPException, Exception) as e:
errors.append(e)
self.parent.footer.set_text("Scanning for duplicate IP address..")
if len(responses["ipaddr"]) > 0:
if self.netsettings[self.activeiface]['link'].upper() != "UP":
try:
network.upIface(self.activeiface)
except NetworkException as e:
errors.append("Cannot activate {0} to check for "
"duplicate IP.".format(self.activeiface))
# Bind arping to requested IP if it's already assigned
assigned_ips = [v.get('addr') for v in
self.netsettings.itervalues()]
arping_bind = responses["ipaddr"] in assigned_ips
if network.duplicateIPExists(responses["ipaddr"],
self.activeiface, arping_bind):
errors.append("Duplicate host found with IP {0}.".format(
responses["ipaddr"]))
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
self.log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
self.log.error("Check failed. Not applying")
self.parent.footer.set_text("Check failed. Not applying.")
self.log.error("%s" % (responses))
return False
self.parent.footer.set_text("Applying changes... (May take up to 20s)")
puppetclasses = []
#If there is a gateway configured in /etc/sysconfig/network, unset it
expr = '^GATEWAY=.*'
replace.replaceInFile("/etc/sysconfig/network", expr, "")
l3ifconfig = {'type': "resource",
'class': "l23network::l3::ifconfig",
'name': self.activeiface}
if responses["onboot"].lower() == "no":
params = {"ipaddr": "none",
"gateway": ""}
elif responses["bootproto"] == "dhcp":
self.unset_gateway()
if "dhcp_nowait" in responses.keys():
params = {"ipaddr": "dhcp",
"dhcp_nowait": responses["dhcp_nowait"]}
else:
params = {"ipaddr": "dhcp"}
else:
cidr = network.netmaskToCidr(responses["netmask"])
params = {"ipaddr": "{0}/{1}".format(responses["ipaddr"], cidr),
"check_by_ping": "none"}
if len(responses["gateway"]) > 1:
params["gateway"] = responses["gateway"]
self.unset_gateway()
l3ifconfig['params'] = params
puppetclasses.append(l3ifconfig)
self.log.info("Puppet data: %s" % (puppetclasses))
try:
self.parent.refreshScreen()
puppet.puppetApply(puppetclasses)
ModuleHelper.getNetwork(self)
gateway = self.get_default_gateway_linux()
if gateway is None:
gateway = ""
self.fixEtcHosts()
except Exception as e:
self.log.error(e)
self.parent.footer.set_text("Error applying changes. Check logs "
"for details.")
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return False
self.parent.footer.set_text("Changes successfully applied.")
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
return True
def getNetwork(self):
ModuleHelper.getNetwork(self)
def getDHCP(self, iface):
return ModuleHelper.getDHCP(iface)
def get_default_gateway_linux(self):
return ModuleHelper.get_default_gateway_linux()
def unset_gateway(self):
"""Unset current gateway."""
command = "ip route del default dev $(ip ro | grep default"\
" | awk '{print $NF}')"
if self.get_default_gateway_linux() is None:
return True
try:
noout = open('/dev/null', 'w')
subprocess.call(command, stdout=noout, stderr=noout,
shell=True)
except OSError:
self.log.warning(traceback.format_exc())
self.log.error("Unable to unset gateway")
self.log.error("Command was: {0}".format(command))
self.parent.footer.set_text("Unable to unset gateway.")
return False
def radioSelectIface(self, current, state, user_data=None):
"""Update network details and display information."""
### This makes no sense, but urwid returns the previous object.
### The previous object has True state, which is wrong.
### Somewhere in current.group a RadioButton is set to True.
### Our quest is to find it.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.activeiface = rb.base_widget.get_label()
break
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
def radioSelect(self, current, state, user_data=None):
"""Update network details and display information."""
### This makes no sense, but urwid returns the previous object.
### The previous object has True state, which is wrong.
### Somewhere in current.group a RadioButton is set to True.
### Our quest is to find it.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.extdhcp = (rb.base_widget.get_label() == "Yes")
break
def setNetworkDetails(self):
self.net_text1.set_text("Interface: %-13s Link: %s" % (
self.activeiface,
self.netsettings[self.activeiface]['link'].upper()))
self.net_text2.set_text("IP: %-15s MAC: %s" % (
self.netsettings[self.activeiface]['addr'],
self.netsettings[self.activeiface]['mac']))
self.net_text3.set_text("Netmask: %-15s Gateway: %s" % (
self.netsettings[self.activeiface]['netmask'],
self.gateway))
#Set text fields to current netsettings
for index, fieldname in enumerate(self.fields):
if fieldname == "ifname":
self.edits[index].base_widget.set_edit_text(self.activeiface)
elif fieldname == "bootproto":
rb_group = self.edits[index].rb_group
for rb in rb_group:
if self.netsettings[self.activeiface]["bootproto"].lower()\
== "dhcp":
rb_group[0].set_state(True)
rb_group[1].set_state(False)
else:
rb_group[0].set_state(False)
rb_group[1].set_state(True)
elif fieldname == "onboot":
rb_group = self.edits[index].rb_group
for rb in rb_group:
if self.netsettings[self.activeiface]["onboot"].lower()\
== "yes":
rb_group[0].set_state(True)
rb_group[1].set_state(False)
else:
#onboot should only be no if the interface is also down
if self.netsettings[self.activeiface]['addr'] == "":
rb_group[0].set_state(False)
rb_group[1].set_state(True)
else:
rb_group[0].set_state(True)
rb_group[1].set_state(False)
elif fieldname == "ipaddr":
self.edits[index].set_edit_text(self.netsettings[
self.activeiface]['addr'])
elif fieldname == "netmask":
self.edits[index].set_edit_text(self.netsettings[
self.activeiface]['netmask'])
elif fieldname == "gateway":
#Gateway is for this iface only if gateway is matches subnet
if network.inSameSubnet(
self.netsettings[self.activeiface]['addr'],
self.gateway,
self.netsettings[self.activeiface]['netmask']):
self.edits[index].set_edit_text(self.gateway)
else:
self.edits[index].set_edit_text("")
def refresh(self):
ModuleHelper.getNetwork(self)
self.setNetworkDetails()
def cancel(self, button):
ModuleHelper.cancel(self, button)
self.setNetworkDetails()
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults, showallbuttons=True)

View File

@ -1,266 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common import dialog
from fuelmenu.common.modulehelper import ModuleHelper
import fuelmenu.common.urwidwrapper as widget
from fuelmenu.settings import Settings
import logging
import re
import subprocess
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.mirrors')
blank = urwid.Divider()
class ntpsetup(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Time Sync"
self.priority = 60
self.visible = True
self.deployment = "pre"
self.parent = parent
#UI details
self.header_content = ["NTP Setup", "Note: If you continue without "
"NTP, you may have issues with deployment "
"due to time synchronization issues. These "
"problems are exacerbated in virtualized "
"environments."]
self.fields = ["ntpenabled", "NTP1", "NTP2", "NTP3"]
self.defaults = \
{
"ntpenabled": {"label": "Enable NTP:",
"tooltip": "",
"value": "radio"},
"NTP1": {"label": "NTP Server 1:",
"tooltip": "NTP Server for time synchronization",
"value": "time.nist.gov"},
"NTP2": {"label": "NTP Server 2:",
"tooltip": "NTP Server for time synchronization",
"value": "time-a.nist.gov"},
"NTP3": {"label": "NTP Server 3:",
"tooltip": "NTP Server for time synchronization",
"value": "time-b.nist.gov"},
}
#Load info
self.gateway = self.get_default_gateway_linux()
self.oldsettings = self.load()
self.screen = None
def check(self, args):
"""Validate that all fields have valid values and sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
#Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname == "blank":
pass
elif fieldname == "ntpenabled":
rb_group = self.edits[index].rb_group
if rb_group[0].state:
responses["ntpenabled"] = "Yes"
else:
responses["ntpenabled"] = "No"
else:
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
warnings = []
if responses['ntpenabled'] == "No":
#Disabled NTP means passing no NTP servers to save method
responses = {
'NTP1': "",
'NTP2': "",
'NTP3': ""}
self.parent.footer.set_text("No errors found.")
log.info("No errors found")
return responses
if all(map(lambda f: (len(responses[f]) == 0), self.fields)):
pass
#We will allow empty if user doesn't need external networking
#and present a strongly worded warning
#msg = "If you continue without NTP, you may have issues with "\
# + "deployment due to time synchronization issues. These "\
# + "problems are exacerbated in virtualized deployments."
#dialog.display_dialog(
# self, widget.TextLabel(msg), "Empty NTP Warning")
del responses['ntpenabled']
for ntpfield, ntpvalue in responses.iteritems():
#NTP must be under 255 chars
if len(ntpvalue) >= 255:
errors.append("%s must be under 255 chars." %
self.defaults[ntpfield]['label'])
#NTP needs to have valid chars
if re.search('[^a-zA-Z0-9-.]', ntpvalue):
errors.append("%s contains illegal characters." %
self.defaults[ntpfield]['label'])
#ensure external NTP is valid
if len(ntpvalue) > 0:
#Validate first NTP address
try:
#Try to test NTP via ntpdate
if not self.checkNTP(ntpvalue):
warnings.append("%s unable to perform NTP."
% self.defaults[ntpfield]['label'])
except Exception:
warnings.append("%s unable to sync time with server.: %s"
% self.defaults[ntpfield]['label'])
if len(errors) > 0:
self.parent.footer.set_text(
"Errors: %s First error: %s" % (len(errors), errors[0]))
log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
if len(warnings) > 0:
msg = ["NTP configuration has the following warnings:"]
msg.extend(warnings)
msg.append("You may see errors during provisioning and "
"in system logs. NTP errors are not fatal.")
warning_msg = '\n'.join(str(line) for line in msg)
dialog.display_dialog(self, widget.TextLabel(warning_msg),
"NTP Warnings")
log.warning(warning_msg)
self.parent.footer.set_text("No errors found.")
log.info("No errors found")
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
return False
self.save(responses)
#Apply NTP now
if len(responses['NTP1']) > 0:
#Stop ntpd, run ntpdate, start ntpd
noout = open('/dev/null', 'w')
subprocess.call(["service", "ntpd", "stop"],
stdout=noout, stderr=noout)
subprocess.call(["ntpdate", "-t5", responses['NTP1']],
stdout=noout, stderr=noout)
subprocess.call(["service", "ntpd", "start"],
stdout=noout, stderr=noout)
return True
def cancel(self, button):
ModuleHelper.cancel(self, button)
def get_default_gateway_linux(self):
return ModuleHelper.get_default_gateway_linux()
def load(self):
#Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
oldsettings = Settings().read(self.parent.settingsfile)
for setting in self.defaults.keys():
try:
if setting == "ntpenabled":
rb_group = \
self.edits[self.fields.index("ntpenabled")].rb_group
if oldsettings[setting]["value"] == "Yes":
rb_group[0].set_state(True)
rb_group[1].set_state(False)
else:
rb_group[0].set_state(False)
rb_group[1].set_state(True)
elif "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
except Exception:
log.warning("No setting named %s found." % setting)
continue
return oldsettings
def save(self, responses):
## Generic settings start ##
newsettings = dict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
newsettings[part1] = self.oldsettings[part1]
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
## Generic settings end ##
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update defaults
for index, fieldname in enumerate(self.fields):
if fieldname != "blank" and fieldname in newsettings:
self.defaults[fieldname]['value'] = newsettings[fieldname]
def checkNTP(self, server):
# Use ntpdate to verify server answers NTP requests
noout = open('/dev/null', 'w')
ntp_works = subprocess.call(["ntpdate", "-q", "-t2", server],
stdout=noout, stderr=noout)
return (ntp_works == 0)
def refresh(self):
self.gateway = self.get_default_gateway_linux()
log.info("refresh. gateway is %s" % self.gateway)
#If gateway is empty, disable NTP
if self.gateway is None:
for index, fieldname in enumerate(self.fields):
if fieldname == "ntpenabled":
log.info("clearing ntp enabled")
log.info("fieldname: %s" % fieldname)
rb_group = self.edits[index].rb_group
rb_group[0].set_state(False)
rb_group[1].set_state(True)
def radioSelect(self, current, state, user_data=None):
"""Update network details and display information."""
### This makes no sense, but urwid returns the previous object.
### The previous object has True state, which is wrong.
### Somewhere in current.group a RadioButton is set to True.
### Our quest is to find it.
for rb in current.group:
if rb.get_label() == current.get_label():
continue
if rb.base_widget.state is True:
self.extdhcp = (rb.base_widget.get_label() == "Yes")
break
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,131 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import crypt
from fuelmenu.common.modulehelper import ModuleHelper
import logging
import subprocess
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.rootpw')
blank = urwid.Divider()
class rootpw(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Root Password"
self.priority = 60
self.visible = True
self.parent = parent
#UI text
self.header_content = ["Set root user password", ""]
self.fields = ["PASSWORD", "CONFIRM_PASSWORD"]
self.defaults = \
{
"PASSWORD": {"label": "Enter password",
"tooltip": "Use ASCII characters only",
"value": ""},
"CONFIRM_PASSWORD": {"label": "Confirm password",
"tooltip": "Use ASCII characters only",
"value": ""},
}
self.screen = None
def check(self, args):
"""Validate that all fields have valid values and sanity checks."""
self.parent.footer.set_text("Checking data...")
self.parent.refreshScreen()
#Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname != "blank":
responses[fieldname] = self.edits[index].get_edit_text()
###Validate each field
errors = []
#Passwords must match
if responses["PASSWORD"] != responses["CONFIRM_PASSWORD"]:
errors.append("Passwords do not match.")
#password must not be empty
if len(responses["PASSWORD"]) == 0:
errors.append("Password must not be empty.")
#password needs to be in ASCII character set
try:
if responses["PASSWORD"].decode('ascii'):
pass
except UnicodeDecodeError:
errors.append("Password contains non-ASCII characters.")
if len(errors) > 0:
self.parent.footer.set_text("Error: %s" % (errors[0]))
log.error("Errors: %s %s" % (len(errors), errors))
return False
else:
self.parent.footer.set_text("No errors found.")
return responses
def apply(self, args):
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
for index, fieldname in enumerate(self.fields):
if fieldname == "PASSWORD":
return (self.edits[index].get_edit_text() == "")
return False
hashed = crypt.crypt(responses["PASSWORD"])
log.info("Changing root password")
try:
#clear any locks first
noout = open('/dev/null', 'w')
subprocess.call(["rm", "-f", "/etc/passwd.lock",
"/etc/shadow.lock"], stdout=noout,
stderr=noout)
retcode = subprocess.call(["usermod", "-p", hashed, "root"],
stdout=noout,
stderr=noout)
except OSError:
log.error("Unable to change password.")
self.parent.footer.set_text("Unable to change password.")
return False
if retcode == 0:
self.parent.footer.set_text("Changed applied successfully.")
log.info("Root password successfully changed.")
#Reset fields
self.cancel(None)
else:
self.parent.footer.set_text("Unable to apply changes. Check logs "
"for more details.")
return False
return True
def cancel(self, button):
ModuleHelper.cancel(self, button)
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,77 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common import dialog
from fuelmenu.common.modulehelper import ModuleHelper
import fuelmenu.common.urwidwrapper as widget
import time
import urwid
import urwid.raw_display
import urwid.web_display
blank = urwid.Divider()
class saveandquit():
def __init__(self, parent):
self.name = "Quit Setup"
self.priority = 99
self.visible = True
self.parent = parent
self.screen = None
#UI text
saveandcontinue_button = widget.Button("Save and Continue",
self.save_and_continue)
saveandquit_button = widget.Button("Save and Quit", self.save_and_quit)
quitwithoutsaving_button = widget.Button("Quit without saving",
self.quit_without_saving)
self.header_content = ["Save configuration before quitting?", blank,
saveandcontinue_button, saveandquit_button,
quitwithoutsaving_button]
self.fields = []
self.defaults = dict()
def save_and_continue(self, args):
self.save()
def save_and_quit(self, args):
if self.save():
self.parent.refreshScreen()
time.sleep(1.5)
self.parent.exit_program(None)
def save(self):
results, modulename = self.parent.global_save()
if results:
self.parent.footer.set_text("All changes saved successfully!")
return True
else:
#show pop up with more details
msg = "ERROR: Module %s failed to save. Go back" % (modulename)\
+ " and fix any mistakes or choose Quit without Saving."
dialog.display_dialog(self, widget.TextLabel(msg),
"Error saving changes!")
return False
def quit_without_saving(self, args):
self.parent.exit_program(None)
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults, buttons_visible=False)

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
try:
from collections import OrderedDict
except Exception:
# python 2.6 or earlier use backport
from ordereddict import OrderedDict
from fuelmenu.common.modulehelper import ModuleHelper
from fuelmenu.common import pwgen
from fuelmenu.settings import Settings
import logging
import urwid
import urwid.raw_display
import urwid.web_display
log = logging.getLogger('fuelmenu.servicepws')
blank = urwid.Divider()
class servicepws(urwid.WidgetWrap):
def __init__(self, parent):
self.name = "Service Passwords"
self.priority = 99
self.visible = False
self.parent = parent
#UI text
self.header_content = ["Set service passwords", ""]
self.defaults = \
{
"astute/user": {"label": "Astute user",
"tooltip": "",
"value": "naily"},
"astute/password": {"label": "Astute password",
"tooltip": "",
"value": pwgen.password()},
"cobbler/user": {"label": "Cobbler user",
"tooltip": "",
"value": "cobbler"},
"cobbler/password": {"label": "Cobbler password",
"tooltip": "",
"value": pwgen.password()},
"keystone/admin_token": {"label": "Keystone Admin Token",
"tooltip": "",
"value": pwgen.password()},
"keystone/nailgun_user": {
"label": "Keystone username for Nailgun",
"tooltip": "",
"value": "nailgun"},
"keystone/nailgun_password": {
"label": "Keystone password for Nailgun",
"tooltip": "",
"value": pwgen.password()},
"keystone/ostf_user": {
"label": "Keystone username for OSTF",
"tooltip": "",
"value": "ostf"},
"keystone/ostf_password": {
"label": "Keystone password for OSTF",
"tooltip": "",
"value": pwgen.password()},
"keystone/monitord_user": {
"label": "Master node monitoring user",
"tooltip": "",
"value": "monitord"
},
"keystone/monitord_password": {
"label": "Master node monitoring password",
"tooltip": "",
"value": pwgen.password(),
},
"mcollective/user": {"label": "Mcollective user",
"tooltip": "",
"value": "mcollective"},
"mcollective/password": {"label": "Mcollective password",
"tooltip": "",
"value": pwgen.password()},
"postgres/keystone_dbname": {"label": "Keystone DB name",
"tooltip": "",
"value": "keystone"},
"postgres/keystone_user": {"label": "Keystone DB user",
"tooltip": "",
"value": "keystone"},
"postgres/keystone_password": {"label": "Keystone DB password",
"tooltip": "",
"value": pwgen.password()},
"postgres/nailgun_dbname": {"label": "Nailgun DB name",
"tooltip": "",
"value": "nailgun"},
"postgres/nailgun_user": {"label": "Nailgun DB user",
"tooltip": "",
"value": "nailgun"},
"postgres/nailgun_password": {"label": "Nailgun DB password",
"tooltip": "",
"value": pwgen.password()},
"postgres/ostf_dbname": {"label": "OSTF DB name",
"tooltip": "",
"value": "ostf"},
"postgres/ostf_user": {"label": "OSTF DB user",
"tooltip": "",
"value": "ostf"},
"postgres/ostf_password": {"label": "OSTF DB password",
"tooltip": "",
"value": pwgen.password()},
}
self.fields = self.defaults.keys()
self.oldsettings = self.load()
self.screen = None
def check(self, args):
#Get field information
responses = dict()
for index, fieldname in enumerate(self.fields):
if fieldname == "blank":
pass
else:
responses[fieldname] = self.edits[index].get_edit_text()
return responses
def apply(self, args):
log.debug('start saving servicepws')
responses = self.check(args)
if responses is False:
log.error("Check failed. Not applying")
log.error("%s" % (responses))
for index, fieldname in enumerate(self.fields):
if fieldname == "PASSWORD":
return (self.edits[index].get_edit_text() == "")
return False
self.save(responses)
def load(self):
#Read in yaml
defaultsettings = Settings().read(self.parent.defaultsettingsfile)
oldsettings = defaultsettings
oldsettings.update(Settings().read(self.parent.settingsfile))
oldsettings = Settings().read(self.parent.settingsfile)
for setting in self.defaults.keys():
try:
if "/" in setting:
part1, part2 = setting.split("/")
self.defaults[setting]["value"] = oldsettings[part1][part2]
else:
self.defaults[setting]["value"] = oldsettings[setting]
except Exception:
log.warning("No setting named %s found." % setting)
continue
return oldsettings
def save(self, responses):
## Generic settings start ##
newsettings = OrderedDict()
for setting in responses.keys():
if "/" in setting:
part1, part2 = setting.split("/")
if part1 not in newsettings:
#We may not touch all settings, so copy oldsettings first
try:
newsettings[part1] = self.oldsettings[part1]
except Exception:
if part1 not in newsettings.keys():
newsettings[part1] = OrderedDict()
log.warning("issues setting newsettings %s " % setting)
log.warning("current newsettings: %s" % newsettings)
newsettings[part1][part2] = responses[setting]
else:
newsettings[setting] = responses[setting]
Settings().write(newsettings,
defaultsfile=self.parent.defaultsettingsfile,
outfn=self.parent.settingsfile)
## Generic settings end ##
log.debug('done saving servicepws')
#Set oldsettings to reflect new settings
self.oldsettings = newsettings
#Update defaults
for index, fieldname in enumerate(self.fields):
if fieldname != "blank" and fieldname in newsettings:
self.defaults[fieldname]['value'] = newsettings[fieldname]
def cancel(self, button):
ModuleHelper.cancel(self, button)
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults)

View File

@ -1,58 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelmenu.common.modulehelper import ModuleHelper
import fuelmenu.common.urwidwrapper as widget
import subprocess
import urwid
import urwid.raw_display
import urwid.web_display
blank = urwid.Divider()
class shell():
def __init__(self, parent):
self.name = "Shell Login"
self.priority = 90
self.visible = True
self.parent = parent
self.screen = None
#UI text
text1 = "Press the button below to enter a shell login."
login_button = widget.Button("Shell Login", self.start_shell)
self.header_content = [text1, blank, login_button]
self.fields = []
self.defaults = dict()
def check(self, args):
return True
def start_shell(self, args):
self.parent.mainloop.screen.stop()
message = "Type exit to return to the main UI."
subprocess.call(
"screen bash -c 'clear; echo %s ; echo; bash -i'" % message,
shell=True
)
self.parent.mainloop.screen.start()
def refresh(self):
pass
def screenUI(self):
return ModuleHelper.screenUI(self, self.header_content, self.fields,
self.defaults, buttons_visible=False)

View File

@ -1,98 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import logging
from ordereddict import OrderedDict
import yaml
def construct_ordered_mapping(self, node, deep=False):
if not isinstance(node, yaml.MappingNode):
raise yaml.ConstructorError(None, None,
"expected a mapping node, but found %s" %
node.id, node.start_mark)
mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
if not isinstance(key, collections.Hashable):
raise yaml.ConstructorError(
"while constructing a mapping", node.start_mark,
"found unhashable key", key_node.start_mark)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping
yaml.constructor.BaseConstructor.construct_mapping = construct_ordered_mapping
def construct_yaml_map_with_ordered_dict(self, node):
data = OrderedDict()
yield data
value = self.construct_mapping(node)
data.update(value)
yaml.constructor.Constructor.add_constructor(
'tag:yaml.org,2002:map',
construct_yaml_map_with_ordered_dict)
def represent_ordered_mapping(self, tag, mapping, flow_style=None):
value = []
node = yaml.MappingNode(tag, value, flow_style=flow_style)
if self.alias_key is not None:
self.represented_objects[self.alias_key] = node
best_style = True
if hasattr(mapping, 'items'):
mapping = list(mapping.items())
for item_key, item_value in mapping:
node_key = self.represent_data(item_key)
node_value = self.represent_data(item_value)
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
best_style = False
if not (isinstance(node_value, yaml.ScalarNode)
and not node_value.style):
best_style = False
value.append((node_key, node_value))
if flow_style is None:
if self.default_flow_style is not None:
node.flow_style = self.default_flow_style
else:
node.flow_style = best_style
return node
yaml.representer.BaseRepresenter.represent_mapping = represent_ordered_mapping
yaml.representer.Representer.add_representer(OrderedDict, yaml.representer.
SafeRepresenter.represent_dict)
class Settings(object):
def read(self, yamlfile):
try:
infile = file(yamlfile, 'r')
settings = yaml.load(infile)
return settings or OrderedDict()
except Exception:
if yamlfile is not None:
logging.error("Unable to read YAML: %s", yamlfile)
return OrderedDict()
def write(self, newvalues, tree=None, defaultsfile='settings.yaml',
outfn='mysettings.yaml'):
settings = self.read(defaultsfile)
settings.update(self.read(outfn))
settings.update(newvalues)
outfile = file(outfn, 'w')
yaml.dump(settings, outfile, default_style='"',
default_flow_style=False)
return True

View File

@ -1,25 +0,0 @@
HOSTNAME: "fuel"
DNS_DOMAIN: "domain.tld"
DNS_SEARCH: "domain.tld"
DNS_UPSTREAM: "8.8.8.8"
NTP1: "0.fuel.pool.ntp.org"
NTP2: "1.fuel.pool.ntp.org"
NTP3: "2.fuel.pool.ntp.org"
ADMIN_NETWORK:
interface: "eth0"
ipaddress: "10.20.0.2"
netmask: "255.255.255.0"
cidr: "10.20.0.0/24"
size: "256"
dhcp_pool_start: "10.20.0.3"
dhcp_pool_end: "10.20.0.254"
FUEL_ACCESS:
user: "admin"
password: "admin"
BOOTSTRAP:
MIRROR_DISTRO: "http://archive.ubuntu.com/ubuntu"
MIRROR_MOS: "http://mirror.fuel-infra.org/mos-repos/ubuntu/7.0"
HTTP_PROXY: ""
EXTRA_APT_REPOS: ""
flavor: "centos"
PRODUCTION: docker

View File

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import ordereddict
from fuelmenu import settings
def test_read_settings(tmpdir):
yaml_file = tmpdir.join("yamlfile.yaml")
yaml_file.write("""
sample:
- one
- a: b
c: d
""")
data = settings.Settings().read(yaml_file.strpath)
assert data == {
'sample': [
'one',
{
'a': 'b',
'c': 'd',
}
]
}
assert isinstance(data, ordereddict.OrderedDict)
@mock.patch('fuelmenu.settings.file', side_effect=Exception('Error'))
def test_read_settings_with_error(_):
data = settings.Settings().read('some_path')
assert data == {}
assert isinstance(data, ordereddict.OrderedDict)

View File

@ -1,20 +0,0 @@
#!/bin/bash
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
set -e
set -x
tox -v

View File

@ -1,52 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import setuptools
setuptools.setup(
name="fuelmenu",
version='8.0.0',
description="Console util for pre-configuration of Fuel server",
author="Matthew Mosesohn",
author_email="mmosesohn@mirantis.com",
classifiers=[
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: Console :: Curses",
"Operating System :: POSIX",
"Programming Language :: Python",
"Topic :: Security",
"Topic :: Internet",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: Proxy Servers",
"Topic :: Software Development :: Testing"
],
install_requires=[
'netaddr>=0.7.5',
'OrderedDict>=1.1',
'PyYAML>=3.10',
'netifaces>=0.5',
'urwid>=1.1.1',
],
include_package_data=True,
packages=setuptools.find_packages(),
entry_points={
'console_scripts': [
'fuelmenu = fuelmenu.fuelmenu:main',
],
},
)

View File

@ -1,47 +0,0 @@
%define name fuelmenu
%{!?version: %define version 8.0.0}
%{!?release: %define release 1}
Name: %{name}
Summary: Console utility for pre-configuration of Fuel server
Version: %{version}
Release: %{release}
License: Apache
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Vendor: Matthew Mosesohn <mmosesohn@mirantis.com>
BuildRequires: python-setuptools
Requires: bind-utils
Requires: nailgun-net-check
Requires: ntp
Requires: python-setuptools
Requires: python-netaddr
Requires: python-netifaces
Requires: python-urwid >= 1.1.0
Requires: PyYAML
Requires: python-ordereddict
Requires: screen
Requires: python-six
%description
Summary: Console utility for pre-configuration of Fuel server
%prep
%setup -cq -n %{name}-%{version}
%build
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py build
%install
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
install -d -m 755 %{buildroot}/etc/fuel
install -m 600 %{_builddir}/%{name}-%{version}/fuelmenu/fuelmenu/settings.yaml %{buildroot}/etc/fuel/astute.yaml
%clean
rm -rf $RPM_BUILD_ROOT
%files -f %{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
%defattr(-,root,root)
%config(noreplace) /etc/fuel/astute.yaml

View File

@ -1,2 +0,0 @@
pytest==2.8.0
mock==1.3.0

View File

@ -1,39 +0,0 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py26,py27,pep8
[testenv]
usedevelop = True
install_command = pip install --allow-external -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
deps =
-r{toxinidir}/test-requirements.txt
commands =
py.test -vv {posargs:fuelmenu/tests}
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:pep8]
deps = hacking==0.7
usedevelop = False
commands =
flake8 {posargs:.}
[testenv:venv]
commands = {posargs:}
[testenv:devenv]
envdir = devenv
usedevelop = True
[flake8]
ignore = H234,H302,H802
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
show-pep8 = True
show-source = True
count = True
[hacking]
import_exceptions = testtools.matchers

View File

@ -432,7 +432,6 @@ function run_extensions_tests {
function run_flake8 {
local result=0
run_flake8_subproject nailgun && \
run_flake8_subproject fuelmenu && \
run_flake8_subproject network_checker && \
run_flake8_subproject fuel_upgrade_system/fuel_upgrade && \
run_flake8_subproject fuel_upgrade_system/fuel_package_updates && \

View File

@ -118,20 +118,16 @@ mv %{_builddir}/%{name}-%{version}/nailgun/compressed_static %{_builddir}/%{name
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py build
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py build
cd %{_builddir}/%{name}-%{version}/shotgun && python setup.py build
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py build
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py build
%install
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/nailgun/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/shotgun && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/shotgun/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/fuelmenu && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates/INSTALLED_FILES
mkdir -p %{buildroot}/opt/nailgun/bin
mkdir -p %{buildroot}/etc/cron.d
mkdir -p %{buildroot}/etc/fuel
install -d -m 755 %{buildroot}/etc/fuel
install -m 600 %{_builddir}/%{name}-%{version}/fuelmenu/fuelmenu/settings.yaml %{buildroot}/etc/fuel/astute.yaml
install -m 755 %{_builddir}/%{name}-%{version}/bin/fencing-agent.rb %{buildroot}/opt/nailgun/bin/fencing-agent.rb
install -m 644 %{_builddir}/%{name}-%{version}/bin/fencing-agent.cron %{buildroot}/etc/cron.d/fencing-agent
install -p -D -m 755 %{_builddir}/%{name}-%{version}/bin/download-debian-installer %{buildroot}%{_bindir}/download-debian-installer
@ -215,38 +211,6 @@ Shotgun package.
%files -n shotgun -f %{_builddir}/%{name}-%{version}/shotgun/INSTALLED_FILES
%defattr(-,root,root)
%package -n fuelmenu
Summary: Console utility for pre-configuration of Fuel server
Version: %{version}
Release: %{release}
License: Apache
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Vendor: Matthew Mosesohn <mmosesohn@mirantis.com>
BuildRequires: python-setuptools
Requires: bind-utils
Requires: nailgun-net-check
Requires: ntp
Requires: python-setuptools
Requires: python-netaddr
Requires: python-netifaces
Requires: python-urwid >= 1.1.0
Requires: PyYAML
Requires: python-ordereddict
Requires: screen
Requires: python-six
%description -n fuelmenu
Summary: Console utility for pre-configuration of Fuel server
%files -n fuelmenu -f %{_builddir}/%{name}-%{version}/fuelmenu/INSTALLED_FILES
%defattr(-,root,root)
%config(noreplace) /etc/fuel/astute.yaml
%package -n fencing-agent
Summary: Fencing agent
Version: %{version}