#!/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 __future__ import absolute_import from fuelmenu.common import dialog from fuelmenu.common import errors from fuelmenu.common import network from fuelmenu.common import timeout from fuelmenu.common import urwidwrapper as widget from fuelmenu.common import utils from fuelmenu import consts from fuelmenu import settings as settings_module import logging import operator from optparse import OptionParser import os import signal import sys import urwid import urwid.raw_display import urwid.web_display # set up logging logging.basicConfig(filename=consts.LOGFILE, 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.managediface = network.get_physical_ifaces()[0] self.dns_might_have_changed = False # Set to true to move all settings to end self.globalsave = True self.version = utils.get_fuel_version() # settings load self.settings = settings_module.Settings() self.settings.load( os.path.join(os.path.dirname(__file__), "settings.yaml"), template_kwargs={"mos_version": self.version}) self.settings.load( consts.SETTINGS_FILE, template_kwargs={"mos_version": self.version}) 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 isinstance(item.base_widget, urwid.Button): if item.base_widget.get_label() == choice: item.set_attr_map({None: 'header'}) else: item.set_attr_map({None: None}) except AttributeError: log.exception("Unable to set menu item %s" % item) self.setChildScreen(name=choice) def draw_child_screen(self, child_screen, focus_on_child=False): self.childpage = child_screen self.childfill = urwid.Filler(self.childpage, 'top', 40) self.childbox = urwid.BoxAdapter(self.childfill, 40) self.cols = urwid.Columns( [ ('fixed', 20, urwid.Pile([ urwid.AttrMap(self.menubox, 'body'), urwid.Divider(" ")])), ('weight', 3, urwid.Pile([ urwid.Divider(" "), self.childbox, urwid.Divider(" ")])) ], 1) if focus_on_child: self.cols.focus_position = 1 # focus on childbox self.child.refresh() self.listwalker[:] = [self.cols] 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.draw_child_screen(self.child.screen) def refreshScreen(self): size = self.screen.get_cols_rows() self.screen.draw_screen(size, self.frame.render(size)) def main(self): text_header = (u"Fuel %s setup " u"Use Up/Down/Left/Right to navigate. F8 exits. " u"Remember to save your changes." % 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, 'body'), 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'), ('header', 'light red', 'light gray', 'bold'), ('footer', 'light red', 'light gray', 'bold'), ('menu', 'black', 'light gray', 'bold'), ('menuf', 'white', 'dark red', 'bold'), ('important', 'light red', 'light gray', ('standout', 'underline')), ('editlbl', 'black', 'light gray'), ('editfc', 'light gray', 'black', 'bold'), ('editbx', 'light gray', 'dark gray'), ('buttn', 'white', 'dark green', 'bold'), ('buttnf', 'light gray', 'dark green', '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): # 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) if success: log.info("Save successful!") else: log.error("Save failed on module %s" % modulename) except timeout.TimeoutError: log.exception("Save on signal timed out. Save not complete.") except KeyboardInterrupt: log.exception("Save was interrupted by the user.") 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)) self.settings.write(outfn=consts.SETTINGS_FILE) return True, None def reload_modules(self): for child in self.children: if hasattr(child, 'load') and callable(child.load): child.load() child.screen = child.screenUI() self.draw_child_screen(child.screen) self.refreshScreen() 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=consts.SETTINGS_FILE): from fuelmenu.common import pwgen import netifaces if utils.is_post_deployment(): print("Not updating settings when invoked during post-deployment.\n" "Run fuelmenu manually to make changes.") sys.exit(0) # 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 dhcp_server_data = network.search_external_dhcp(iface, dhcptimeout) except errors.NetworkException: log.error("DHCP scan failed") dhcp_server_data = [] 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)) default_settings_file = os.path.join(os.path.dirname(__file__), "settings.yaml") mos_version = utils.get_fuel_version() settings = settings_module.Settings() settings.load( default_settings_file, template_kwargs={"mos_version": mos_version}) settings.load(settingsfile, template_kwargs={"mos_version": mos_version}) settings_upd = \ { "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, "ADMIN_NETWORK/ssh_network": network.getCidr(ip, netmask), "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_upd.keys(): if "/" in setting: part1, part2 = setting.split("/") settings.setdefault(part1, {}) # Keep old values for passwords if already set if "password" in setting: settings[part1].setdefault(part2, settings_upd[setting]) else: settings[part1][part2] = settings_upd[setting] else: if "password" in setting: settings.setdefault(setting, settings_upd[setting]) else: settings[setting] = settings_upd[setting] # Write astute.yaml settings.write(outfn=settingsfile) def main(*args, **kwargs): if urwid.VERSION < (1, 1, 0): print("This program requires urwid 1.1.0 or greater.") try: default_iface = network.get_physical_ifaces()[0] except IndexError: print("Unable to detect any network interfaces. Could not start") sys.exit(1) 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=default_iface, help="Set IFACE as primary.") options, args = parser.parse_args() if not network.is_interface_has_ip(options.iface): print("Selected interface '{0}' has no assigned IP. " "Could not start.".format(options.iface)) sys.exit(1) if options.save_only: save_only(options.iface) else: setup() if '__main__' == __name__ or urwid.web_display.is_web_request(): setup()