Fabian Deutsch has uploaded a new change for review. Change subject: defaults: Build objects around configuration ......................................................................
defaults: Build objects around configuration Each configuration item (e.g. network, snmp, ...) can now be configured using the appropriate defaults.* class. Change-Id: I684d85f1689ab97d22e5be4b5c740676751464c1 Signed-off-by: Fabian Deutsch <[email protected]> --- M scripts/tui/bin/ovirt-config-installer M scripts/tui/bin/ovirt-config-setup R scripts/tui/src/ovirt/node/app.py D scripts/tui/src/ovirt/node/app/installer.py M scripts/tui/src/ovirt/node/base.py M scripts/tui/src/ovirt/node/config/__init__.py M scripts/tui/src/ovirt/node/config/defaults.py R scripts/tui/src/ovirt/node/installer/__init__.py R scripts/tui/src/ovirt/node/plugins.py D scripts/tui/src/ovirt/node/plugins/setup/__init__.py A scripts/tui/src/ovirt/node/setup/__init__.py R scripts/tui/src/ovirt/node/setup/__main__.py R scripts/tui/src/ovirt/node/setup/engine_page.py R scripts/tui/src/ovirt/node/setup/example.py R scripts/tui/src/ovirt/node/setup/features.py R scripts/tui/src/ovirt/node/setup/kdump_page.py R scripts/tui/src/ovirt/node/setup/keyboard_page.py R scripts/tui/src/ovirt/node/setup/logging_page.py R scripts/tui/src/ovirt/node/setup/monitoring_page.py R scripts/tui/src/ovirt/node/setup/network_page.py R scripts/tui/src/ovirt/node/setup/ping.py R scripts/tui/src/ovirt/node/setup/remote_storage_page.py R scripts/tui/src/ovirt/node/setup/security_page.py R scripts/tui/src/ovirt/node/setup/snmp_page.py R scripts/tui/src/ovirt/node/setup/status_page.py R scripts/tui/src/ovirt/node/setup/support_page.py R scripts/tui/src/ovirt/node/setup/usage.py M scripts/tui/src/ovirt/node/ui/__init__.py M scripts/tui/src/ovirt/node/ui/builder.py M scripts/tui/src/ovirt/node/ui/tui.py M scripts/tui/src/ovirt/node/utils/fs.py M scripts/tui/src/ovirt/node/utils/security.py M scripts/tui/src/ovirt/node/valid.py 33 files changed, 484 insertions(+), 210 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/32/9932/1 diff --git a/scripts/tui/bin/ovirt-config-installer b/scripts/tui/bin/ovirt-config-installer index f50eca4..9f97418 100755 --- a/scripts/tui/bin/ovirt-config-installer +++ b/scripts/tui/bin/ovirt-config-installer @@ -5,4 +5,4 @@ #export PYTHONUNBUFFERED=x #export PYTHONDEBUG= #export PYTHONVERBOSE=x -python -t -m ovirt.node.app.installer "$@" +python -t -m ovirt.node.installer "$@" diff --git a/scripts/tui/bin/ovirt-config-setup b/scripts/tui/bin/ovirt-config-setup index af0610b..0a65367 100755 --- a/scripts/tui/bin/ovirt-config-setup +++ b/scripts/tui/bin/ovirt-config-setup @@ -5,4 +5,4 @@ #export PYTHONUNBUFFERED=x #export PYTHONDEBUG= #export PYTHONVERBOSE=x -python -t -m ovirt.node.app.setup "$@" +python -t -m ovirt.node.setup "$@" diff --git a/scripts/tui/src/ovirt/node/app/__init__.py b/scripts/tui/src/ovirt/node/app.py similarity index 62% rename from scripts/tui/src/ovirt/node/app/__init__.py rename to scripts/tui/src/ovirt/node/app.py index 92f57fc..63be409 100644 --- a/scripts/tui/src/ovirt/node/app/__init__.py +++ b/scripts/tui/src/ovirt/node/app.py @@ -24,46 +24,69 @@ which communicate with each other. """ +import argparse import logging logging.basicConfig(level=logging.DEBUG, filename="app.log", filemode="w", format="%(asctime)s %(levelname)s %(name)s %(message)s") -LOGGER = logging.getLogger(__name__) import ovirt.node.ui.tui -import ovirt.node.utils -import ovirt.node.plugins +from ovirt.node import base, utils, plugins +from ovirt.node.config import defaults -class Application(object): +class Application(base.Base): plugins = [] ui = None def __init__(self, plugin_base, ui_backend="urwid"): + super(Application, self).__init__() + self.__parse_cmdline() + ui_backend_class = { "urwid": ovirt.node.ui.tui.UrwidTUI }[ui_backend] self.ui = ui_backend_class(self) self.plugin_base = plugin_base + def __parse_cmdline(self): + parser = argparse.ArgumentParser(description='oVirt Node Utility') + parser.add_argument("--config", + type=str, + help="Central oVirt Node configuration file") + args = parser.parse_args() + self.logger.debug("Parsed args: %s" % args) + if args.config: + defaults.OVIRT_NODE_DEFAULTS_FILENAME = args.config + self.logger.debug("Setting config file: %s (%s)" % ( + args.config, + defaults.OVIRT_NODE_DEFAULTS_FILENAME)) + def __load_plugins(self): - self.plugins = [m.Plugin(self) for m in ovirt.node.plugins.load(self.plugin_base)] + self.plugins = [] + for m in plugins.load(self.plugin_base): + if hasattr(m, "Plugin"): + self.logger.debug("Found plugin in module: %s" % m) + plugin = m.Plugin(self) + self.plugins.append(plugin) + else: + self.logger.debug("Found no plugin in module: %s" % m) for plugin in self.plugins: - LOGGER.debug("Loading plugin %s" % plugin) + self.logger.debug("Loading plugin %s" % plugin) self.ui.register_plugin(plugin.ui_name(), plugin) def __drop_to_shell(self): with self.ui.suspended(): - ovirt.node.utils.process.system("reset ; bash") + utils.process.system("reset ; bash") def __check_terminal_size(self): cols, rows = self.ui.size() if cols < 80 or rows < 24: - LOGGER.warning("Window size is too small: %dx%d" % (cols, rows)) + self.logger.warning("Window size is too small: %dx%d" % (cols, rows)) def model(self, plugin_name): model = None @@ -82,5 +105,5 @@ self.ui.run() def quit(self): - LOGGER.info("Quitting") - self.ui.quit() \ No newline at end of file + self.logger.info("Quitting") + self.ui.quit() diff --git a/scripts/tui/src/ovirt/node/app/installer.py b/scripts/tui/src/ovirt/node/app/installer.py deleted file mode 100644 index ae6e730..0000000 --- a/scripts/tui/src/ovirt/node/app/installer.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/python -# -# ovirt-config-setup.py - Copyright (C) 2012 Red Hat, Inc. -# Written by Fabian Deutsch <[email protected]> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -""" -Create an installer application instance an start it. -""" - -import ovirt.node.app -import ovirt.node.plugins.installer - - -if __name__ == '__main__': - app = ovirt.node.app.Application(ovirt.node.plugins.installer) - app.run() diff --git a/scripts/tui/src/ovirt/node/base.py b/scripts/tui/src/ovirt/node/base.py index 7b8c055..87d0138 100644 --- a/scripts/tui/src/ovirt/node/base.py +++ b/scripts/tui/src/ovirt/node/base.py @@ -35,4 +35,4 @@ def __init__(self): """Contructor.""" - self._logger = logging.getLogger(self.__module__) \ No newline at end of file + self._logger = logging.getLogger(self.__module__) diff --git a/scripts/tui/src/ovirt/node/config/__init__.py b/scripts/tui/src/ovirt/node/config/__init__.py index 5bcef55..cc4a463 100644 --- a/scripts/tui/src/ovirt/node/config/__init__.py +++ b/scripts/tui/src/ovirt/node/config/__init__.py @@ -1,3 +1,6 @@ """ This package is expected to contain modules which handle locale config files. -""" \ No newline at end of file + +All the informations provided by any module in this package must be derived +from some file. +""" diff --git a/scripts/tui/src/ovirt/node/config/defaults.py b/scripts/tui/src/ovirt/node/config/defaults.py index 44f0aa1..9385e0e 100644 --- a/scripts/tui/src/ovirt/node/config/defaults.py +++ b/scripts/tui/src/ovirt/node/config/defaults.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# model.py - Copyright (C) 2012 Red Hat, Inc. +# defaults.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ There are classes for all components which can be configured through that central configuration file. Each class (for a component) can have a configure and apply_config method. Look -at the CentralNodeConfiguration for more informations. +at the CentralNodeConfiguration for more informations. Each class should implement a configure method, mainly to define all the required arguments (or keys). @@ -37,9 +37,8 @@ import logging import glob -import ovirt.node.utils import ovirt.node.config -from ovirt.node import base +from ovirt.node import base, exceptions, valid, utils LOGGER = logging.getLogger(__name__) @@ -47,93 +46,187 @@ OVIRT_NODE_DEFAULTS_FILENAME = "/etc/defaults/ovirt" -def defaults(new_dict=None, filename=OVIRT_NODE_DEFAULTS_FILENAME, - remove_empty=False): - """Reads /etc/defaults/ovirt and creates a dictionary - The dict will contain all OVIRT_* entries of the defaults file. +class AugeasProvider(base.Base): + def __init__(self, filename): + super(AugeasProvider, self).__init__() + self.filename = filename - Args: - new_dict: New values to be used for setting the defaults - filename: The filename to read the defaults from - remove_empty: Remove a key from defaults file, if the new value is None - Returns: - A dict + def update(self, new_dict, remove_empty): + aug = utils.AugeasWrapper() + basepath = "/files/%s/" % self.filename.strip("/") + if new_dict: + # If values are given, update the file + LOGGER.debug("Updating oVirtNode defaults file '%s': %s %s" % ( + self.filename, + new_dict, + basepath)) + aug.set_many(new_dict, basepath) + + if remove_empty: + paths_to_be_removed = [p for p, v in new_dict.items() + if v is None] + aug.remove_many(paths_to_be_removed, basepath) + + def get_dict(self): + aug = utils.AugeasWrapper() + basepath = "/files/%s/" % self.filename.strip("/") + + # Retrieve all entries of the default file and return their values + paths = aug.match(basepath + "*") + return aug.get_many(paths, strip_basepath=basepath) + + +class SimpleProvider(base.Base): + """Can write our simple configuration file + + >>> fn = "/tmp/cfg_dummy.simple" + >>> open(fn, "w").close() + >>> cfg = { + ... "IP_ADDR": "127.0.0.1", + ... "NETMASK": "255.255.255.0", + ... } + >>> p = SimpleProvider(fn) + >>> p.get_dict() + {} + >>> p.update(cfg, True) + >>> p.get_dict() == cfg + True """ + def __init__(self, filename): + super(SimpleProvider, self).__init__() + self.filename = filename + self.logger.debug("Using %s" % self.filename) - aug = ovirt.node.utils.AugeasWrapper() - basepath = "/files/%s/" % filename.strip("/") - if new_dict: - # If values are given, update the file - LOGGER.debug("Updating oVirtNode defaults file '%s': %s %s" % ( - filename, - new_dict, - basepath)) - aug.set_many(new_dict, basepath) + def update(self, new_dict, remove_empty): + cfg = self.get_dict() + cfg.update(new_dict) - if remove_empty: - paths_to_be_removed = [p for p, v in new_dict.items() if v is None] - aug.remove_many(paths_to_be_removed, basepath) + for key, value in cfg.items(): + if remove_empty and value is None: + del cfg[key] + assert type(value) in [str, unicode] or value is None + self._write(cfg) - # Retrieve all entries of the default file and return their values - paths = aug.match(basepath + "*") - return aug.get_many(paths, strip_basepath=basepath) + def get_dict(self): + cfg = {} + with open(self.filename) as source: + for line in source: + if line.startswith("#"): + continue + key, value = line.split("=", 1) + cfg[key] = value.strip("\"' \n") + return cfg + + def _write(self, cfg): + # FIXME make atomic + contents = [] + # Sort the dict, looks nicer + for key in sorted(cfg.iterkeys()): + contents.append("%s='%s'" % (key, cfg[key])) + with open(self.filename, "w+") as dst: + dst.write("\n".join(contents)) -def map_and_update_defaults(func): - """ - >>> class Foo(object): - ... keys = None - ... def _map_config_and_update_defaults(self, *args, **kwargs): - ... return kwargs - ... @map_and_update_defaults - ... def meth(self, a, b): - ... assert type(a) is int - ... assert type(b) is int - >>> foo = Foo() - >>> foo.keys = ("OVIRT_A", "OVIRT_B") - >>> foo.meth(1, 2) - {'OVIRT_A': 1, 'OVIRT_B': 2} - """ - def wrapper(self, *args, **kwargs): - new_dict = dict(zip(self.keys, args)) - func(self, *args, **kwargs) - return self._map_config_and_update_defaults(**new_dict) - return wrapper +class ConfigFile(base.Base): + def __init__(self, filename=None, provider_class=None): + super(ConfigFile, self).__init__() + filename = filename or OVIRT_NODE_DEFAULTS_FILENAME + provider_class = provider_class or SimpleProvider + self.provider = provider_class(filename) + + def update(self, new_dict, remove_empty=False): + """Reads /etc/defaults/ovirt and creates a dictionary + The dict will contain all OVIRT_* entries of the defaults file. + + Args: + new_dict: New values to be used for setting the defaults + filename: The filename to read the defaults from + remove_empty: Remove a key from defaults file, if the new value + is None + Returns: + A dict + """ + self.logger.debug("Updating defaults: %s" % new_dict) + self.logger.debug("Removing empty entries? %s" % remove_empty) + self.provider.update(new_dict, remove_empty) + + def get_dict(self): + return self.provider.get_dict() class CentralNodeConfiguration(base.Base): - def __init__(self, keys): - assert type(keys) is tuple, "Keys need to have an order, " + \ - "therefor a tuple expected" - self.keys = keys + def __init__(self, cfgfile=None): + super(CentralNodeConfiguration, self).__init__() + self.defaults = cfgfile or ConfigFile() - def configure(self, *args, **kwargs): + def update(self, *args, **kwargs): """This function set's the correct entries in the defaults file for that specififc subclass. Is expected to call _map_config_and_update_defaults() """ raise NotImplementedError - def _map_config_and_update_defaults(self, *args, **kwargs): - assert len(args) == 0 - assert (set(self.keys) ^ set(kwargs.keys())) == set() - new_dict = {k.upper(): v for k, v in kwargs.items()} - defaults(new_dict, remove_empty=True) - - def apply_config(self, *args, **kwargs): - """This method updates the to this subclass specififc configuration + def apply(self, *args, **kwargs): + """This method updates the to this subclass specific configuration files according to the config keys set with configure. """ raise NotImplementedError - def get_config(self): + def retrieve(self): """Returns the config keys of the current component """ - items = {} - for key, value in defaults().items(): - if key in self.keys: - items[key] = value - return items + func = self.update.wrapped_func + varnames = func.func_code.co_varnames[1:] + values = () + cfg = self.defaults.get_dict() + for key in self.keys: + value = cfg[key] if key in cfg else "" + values += (value,) + assert len(varnames) == len(values) + return zip(varnames, values) + + def clear(self): + """Remove the configuration for this item + """ + cfg = self.defaults.get_dict() + to_be_deleted = {k: None for k in self.keys} + cfg.update(to_be_deleted) + self.defaults.update(cfg, remove_empty=True) + + def _map_config_and_update_defaults(self, *args, **kwargs): + assert len(args) == 0 + assert (set(self.keys) ^ set(kwargs.keys())) == set() + new_dict = {k.upper(): v for k, v in kwargs.items()} + self.defaults.update(new_dict, remove_empty=True) + + @staticmethod + def map_and_update_defaults_decorator(func): + """ + >>> class Foo(object): + ... keys = None + ... def _map_config_and_update_defaults(self, *args, **kwargs): + ... return kwargs + ... @CentralNodeConfiguration.map_and_update_defaults_decorator + ... def meth(self, a, b, c): + ... assert type(a) is int + ... assert type(b) is int + ... return {"OVIRT_C": "c%s" % c} + >>> foo = Foo() + >>> foo.keys = ("OVIRT_A", "OVIRT_B", "OVIRT_C") + >>> foo.meth(1, 2, 3) + {'OVIRT_A': 1, 'OVIRT_B': 2, 'OVIRT_C': 'c3'} + """ + def wrapper(self, *args, **kwargs): + if len(self.keys) != len(args): + raise Exception("There are not enough arguments given for " + + "%s of %s" % (func, self)) + new_cfg = dict(zip(self.keys, args)) + custom_cfg = func(self, *args, **kwargs) or {} + assert type(custom_cfg) is dict, "%s must return a dict" % func + new_cfg.update(custom_cfg) + return self._map_config_and_update_defaults(**new_cfg) + wrapper.wrapped_func = func + return wrapper class Network(CentralNodeConfiguration): @@ -142,29 +235,72 @@ - OVIRT_IP_ADDRESS, OVIRT_IP_NETMASK, OVIRT_IP_GATEWAY - OVIRT_VLAN - OVIRT_IPV6 + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> n = Network(cfgfile) + >>> n.update("eth0", "static", "10.0.0.1", "255.0.0.0", "10.0.0.255", + ... "20") + >>> data = n.retrieve() + >>> data[:3] + [('iface', 'eth0'), ('bootproto', 'static'), ('ipaddr', '10.0.0.1')] + >>> data [3:] + [('netmask', '255.0.0.0'), ('gw', '10.0.0.255'), ('vlanid', '20')] + + >>> n.clear() + >>> data = n.retrieve() + >>> data [:3] + [('iface', None), ('bootproto', None), ('ipaddr', None)] + >>> data [3:] + [('netmask', None), ('gw', None), ('vlanid', None)] """ keys = ("OVIRT_BOOTIF", "OVIRT_BOOTPROTO", "OVIRT_IP_ADDRESS", - "OVIRT_IP_NETMASK", - "OVIRT_IP_GATEWAY", + "OVIRT_NETMASK", + "OVIRT_GATEWAY", "OVIRT_VLAN") - @map_and_update_defaults - def configure(self, iface, bootproto, ipaddr=None, netmask=None, gw=None, + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, iface, bootproto, ipaddr=None, netmask=None, gw=None, vlanid=None): - pass + if bootproto not in ["static", "none", "dhcp"]: + raise exceptions.InvalidData("Unknown bootprotocol: %s" % + bootproto) + (valid.IPv4Address() | valid.Empty())(ipaddr) + (valid.IPv4Address() | valid.Empty())(netmask) + (valid.IPv4Address() | valid.Empty())(gw) class Nameservers(CentralNodeConfiguration): - keys = ("OVIRT_DNS") + """Configure nameservers + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> servers = ["10.0.0.2", "10.0.0.3"] + >>> n = Nameservers(cfgfile) + >>> n.update(servers) + >>> data = n.retrieve() + >>> all([servers[idx] == s for idx, s in enumerate(data["servers"])]) + True + """ + keys = ("OVIRT_DNS",) - @map_and_update_defaults - def configure(self, servers): - pass + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, servers): + assert type(servers) is list + servers = filter(lambda i: i.strip() not in ["", None], servers) + map(valid.IPv4Address(), servers) + return { + "OVIRT_DNS": ",".join(servers) + } + def retrieve(self): + cfg = dict(CentralNodeConfiguration.retrieve(self)) + return { + "servers": cfg["servers"].split(",") + } - def apply_config(self): + def apply(self): """Derives the nameserver config from OVIRT_DNS 1. Parse nameservers from defaults @@ -175,7 +311,7 @@ Args: servers: List of servers (str) """ - ovirt_config = defaults() + ovirt_config = self.defaults.get_dict() if "OVIRT_DNS" not in ovirt_config: self.logger.debug("No DNS server entry in default config") return @@ -186,7 +322,7 @@ servers = servers.split(",") - aug = ovirt.node.utils.AugeasWrapper() + aug = utils.AugeasWrapper() # Write resolv.conf any way, sometimes without servers comment = ("Please make changes through the TUI. " + \ "Manual edits to this file will be " + \ @@ -206,33 +342,79 @@ else: aug.remove(path) - ovirt.node.utils.fs.persist_config("/etc/resolv.conf") + utils.fs.persist_config("/etc/resolv.conf") class Timeservers(CentralNodeConfiguration): - keys = ("OVIRT_NTP") + """Configure timeservers - @map_and_update_defaults - def configure(self, servers): - pass + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> servers = ["10.0.0.4", "10.0.0.5"] + >>> n = Timeservers(cfgfile) + >>> n.update(servers) + >>> data = n.retrieve() + >>> all([servers[idx] == s for idx, s in enumerate(data["servers"])]) + True + """ + keys = ("OVIRT_NTP",) + + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, servers): + assert type(servers) is list + servers = filter(lambda i: i.strip() not in ["", None], servers) + map(valid.IPv4Address(), servers) + return { + "OVIRT_NTP": ",".join(servers) + } + + def retrieve(self): + cfg = dict(CentralNodeConfiguration.retrieve(self)) + return { + "servers": cfg["servers"].split(",") + } class Syslog(CentralNodeConfiguration): + """Configure rsyslog + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> server = "10.0.0.6" + >>> port = "514" + >>> n = Syslog(cfgfile) + >>> n.update(server, port) + >>> n.retrieve() + [('server', '10.0.0.6'), ('port', '514')] + """ keys = ("OVIRT_SYSLOG_SERVER", "OVIRT_SYSLOG_PORT") - @map_and_update_defaults - def configure(self, server, port): - pass + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, server, port): + valid.FQDNOrIPAddress()(server) + valid.Port()(port) class Collectd(CentralNodeConfiguration): + """Configure collectd + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> server = "10.0.0.7" + >>> port = "42" + >>> n = Collectd(cfgfile) + >>> n.update(server, port) + >>> n.retrieve() + [('server', '10.0.0.7'), ('port', '42')] + """ keys = ("OVIRT_COLLECTD_SERVER", "OVIRT_COLLECTD_PORT") - @map_and_update_defaults - def configure(self, server, port): - pass + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, server, port): + valid.FQDNOrIPAddress()(server) + valid.Port()(port) class RHN(CentralNodeConfiguration): @@ -248,52 +430,110 @@ "OVIRT_RHN_PROXYUSER", "OVIRT_RHN_PROXYPASSWORD") - @map_and_update_defaults - def configure(self, rhntype, url, ca_cert, username, password, profile, + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, rhntype, url, ca_cert, username, password, profile, activationkey, org, proxy, proxyuser, proxypassword): pass class KDump(CentralNodeConfiguration): + """Configure kdump + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> nfs_url = "host.example.com" + >>> ssh_url = "[email protected]" + >>> n = KDump(cfgfile) + >>> n.update(nfs_url, ssh_url) + >>> n.retrieve() + [('nfs', 'host.example.com'), ('ssh', '[email protected]')] + """ keys = ("OVIRT_KDUMP_NFS", "OVIRT_KDUMP_SSH") - @map_and_update_defaults - def configure(self, nfs, ssh): - pass + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, nfs, ssh): + valid.FQDNOrIPAddress()(nfs) + valid.URL()(ssh) class iSCSI(CentralNodeConfiguration): + """Configure iSCSI + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> n = iSCSI(cfgfile) + >>> n.update("node.example.com", "target.example.com", "10.0.0.8", "42") + >>> data = n.retrieve() + >>> data[:2] + [('name', 'node.example.com'), ('target_name', 'target.example.com')] + >>> data[2:] + [('target_host', '10.0.0.8'), ('target_port', '42')] + """ keys = ("OVIRT_ISCSI_NODE_NAME", "OVIRT_ISCSI_TARGET_NAME", "OVIRT_ISCSI_TARGET_IP", "OVIRT_ISCSI_TARGET_PORT") - @map_and_update_defaults - def configure(self, name, target_name, target_host, target_port): + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, name, target_name, target_host, target_port): + # FIXME add validation pass class SNMP(CentralNodeConfiguration): - keys = ("OVIRT_SNMP_PASSWORD") + """Configure SNMP - @map_and_update_defaults - def configure(self, password): + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> n = SNMP(cfgfile) + >>> n.update("secret") + >>> n.retrieve() + [('password', 'secret')] + """ + keys = ("OVIRT_SNMP_PASSWORD",) + + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, password): + # FIXME add validation pass class Netconsole(CentralNodeConfiguration): + """Configure netconsole + + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> n = Netconsole(cfgfile) + >>> server = "10.0.0.9" + >>> port = "666" + >>> n.update(server, port) + >>> n.retrieve() + [('server', '10.0.0.9'), ('port', '666')] + """ keys = ("OVIRT_NETCONSOLE_SERVER", "OVIRT_NETCONSOLE_PORT") - @map_and_update_defaults - def configure(self, server, port): + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, server, port): + # FIXME add validation pass class CIM(CentralNodeConfiguration): - keys = ("OVIRT_CIM_ENABLED") + """Configure CIM - @map_and_update_defaults - def configure(self, enabled): - assert enabled in ["1", "0"] \ No newline at end of file + >>> fn = "/tmp/cfg_dummy" + >>> cfgfile = ConfigFile(fn, SimpleProvider) + >>> n = CIM(cfgfile) + >>> n.update(True) + >>> n.retrieve() + [('enabled', '1')] + """ + keys = ("OVIRT_CIM_ENABLED",) + + @CentralNodeConfiguration.map_and_update_defaults_decorator + def update(self, enabled): + return { + "OVIRT_CIM_ENABLED": "1" if utils.parse_bool(enabled) else "0" + } diff --git a/scripts/tui/src/ovirt/node/plugins/installer/__init__.py b/scripts/tui/src/ovirt/node/installer/__init__.py similarity index 98% rename from scripts/tui/src/ovirt/node/plugins/installer/__init__.py rename to scripts/tui/src/ovirt/node/installer/__init__.py index a38f201..f5390bc 100644 --- a/scripts/tui/src/ovirt/node/plugins/installer/__init__.py +++ b/scripts/tui/src/ovirt/node/installer/__init__.py @@ -1,3 +1,3 @@ """ This package contains all UI plugins for the installer -""" \ No newline at end of file +""" diff --git a/scripts/tui/src/ovirt/node/plugins/__init__.py b/scripts/tui/src/ovirt/node/plugins.py similarity index 92% rename from scripts/tui/src/ovirt/node/plugins/__init__.py rename to scripts/tui/src/ovirt/node/plugins.py index a4b52d2..b8819d5 100644 --- a/scripts/tui/src/ovirt/node/plugins/__init__.py +++ b/scripts/tui/src/ovirt/node/plugins.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# __init__.py - Copyright (C) 2012 Red Hat, Inc. +# plugins.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -40,7 +40,9 @@ modules = [] for importer, modname, ispkg in __walk_plugins(basemodule): #print("Found submodule %s (is a package: %s)" % (modname, ispkg)) - module = __import__(basemodule.__name__ + "." + modname, fromlist="dummy") + modpath = basemodule.__name__ + "." + modname + module = __import__(modpath, + fromlist="dummy") #print("Imported", module) modules += [module] return modules @@ -189,8 +191,8 @@ except NotImplementedError: self.logger.debug("Plugin has no model") except ovirt.node.exceptions.InvalidData: - self.logger.warning("Plugins model does not pass sematic check: %s" % \ - model) + self.logger.warning("Plugins model does not pass sematic " + + "check: %s" % model) is_valid = False finally: self.__changes = {} @@ -216,12 +218,14 @@ if type(change) is not dict: self.logger.warning("Change is not a dict: %s" % change) - self.logger.debug("Passing UI change to callback on_change: %s" % change) + self.logger.debug("Passing UI change to callback on_change: %s" % \ + change) if self.validate_changes: self.validate(change) self.on_change(change) self.__changes.update(change) - self.logger.debug("Sum of all UI changes up to now: %s" % self.__changes) + self.logger.debug("Sum of all UI changes up to now: %s" % \ + self.__changes) return True def _on_ui_save(self): @@ -230,8 +234,9 @@ """ self.logger.debug("Request to apply model changes") effective_changes = self.pending_changes() or {} - successfull_merge = self.on_merge(effective_changes) + successfull_merge = self.on_merge(effective_changes) is not False if successfull_merge: + self.logger.info("Changes were merged successfully") self.__changes = {} return successfull_merge @@ -259,8 +264,9 @@ model = self.model() for key, value in self.__changes.items(): if key in model and value == model[key]: - self.logger.debug(("Skipping pseudo-change of '%s', value " + \ - "(%s) did not change") % (key, value)) + self.logger.debug(("Skipping pseudo-change of '%s', " + \ + "value (%s) did not change") % (key, + value)) else: effective_changes[key] = value else: diff --git a/scripts/tui/src/ovirt/node/plugins/setup/__init__.py b/scripts/tui/src/ovirt/node/plugins/setup/__init__.py deleted file mode 100644 index 32f8845..0000000 --- a/scripts/tui/src/ovirt/node/plugins/setup/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This package contains all UI plugins for the setup -""" \ No newline at end of file diff --git a/scripts/tui/src/ovirt/node/setup/__init__.py b/scripts/tui/src/ovirt/node/setup/__init__.py new file mode 100644 index 0000000..7fafb86 --- /dev/null +++ b/scripts/tui/src/ovirt/node/setup/__init__.py @@ -0,0 +1,3 @@ +""" +This package contains all plugins and the __main__ for the setup application +""" diff --git a/scripts/tui/src/ovirt/node/app/setup.py b/scripts/tui/src/ovirt/node/setup/__main__.py similarity index 88% rename from scripts/tui/src/ovirt/node/app/setup.py rename to scripts/tui/src/ovirt/node/setup/__main__.py index 074391b..e15714f 100644 --- a/scripts/tui/src/ovirt/node/app/setup.py +++ b/scripts/tui/src/ovirt/node/setup/__main__.py @@ -22,10 +22,9 @@ Create an setup application instance an start it. """ -import ovirt.node.app -import ovirt.node.plugins.setup +from ovirt.node import app, setup if __name__ == '__main__': - app = ovirt.node.app.Application(ovirt.node.plugins.setup) + app = app.Application(setup) app.run() diff --git a/scripts/tui/src/ovirt/node/plugins/setup/engine_page.py b/scripts/tui/src/ovirt/node/setup/engine_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/engine_page.py rename to scripts/tui/src/ovirt/node/setup/engine_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/example.py b/scripts/tui/src/ovirt/node/setup/example.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/example.py rename to scripts/tui/src/ovirt/node/setup/example.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/features.py b/scripts/tui/src/ovirt/node/setup/features.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/features.py rename to scripts/tui/src/ovirt/node/setup/features.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/kdump_page.py b/scripts/tui/src/ovirt/node/setup/kdump_page.py similarity index 98% rename from scripts/tui/src/ovirt/node/plugins/setup/kdump_page.py rename to scripts/tui/src/ovirt/node/setup/kdump_page.py index 8d7d03e..5cdfa64 100644 --- a/scripts/tui/src/ovirt/node/plugins/setup/kdump_page.py +++ b/scripts/tui/src/ovirt/node/setup/kdump_page.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# kdump.py - Copyright (C) 2012 Red Hat, Inc. +# kdump_page.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify diff --git a/scripts/tui/src/ovirt/node/plugins/setup/keyboard_page.py b/scripts/tui/src/ovirt/node/setup/keyboard_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/keyboard_page.py rename to scripts/tui/src/ovirt/node/setup/keyboard_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/logging_page.py b/scripts/tui/src/ovirt/node/setup/logging_page.py similarity index 98% rename from scripts/tui/src/ovirt/node/plugins/setup/logging_page.py rename to scripts/tui/src/ovirt/node/setup/logging_page.py index 4a2f86e..0f3783c 100644 --- a/scripts/tui/src/ovirt/node/plugins/setup/logging_page.py +++ b/scripts/tui/src/ovirt/node/setup/logging_page.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# logging.py - Copyright (C) 2012 Red Hat, Inc. +# logging_page.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -26,6 +26,7 @@ import ovirt.node.valid import ovirt.node.ui + class Plugin(ovirt.node.plugins.NodePlugin): _model = None _widgets = None diff --git a/scripts/tui/src/ovirt/node/plugins/setup/monitoring_page.py b/scripts/tui/src/ovirt/node/setup/monitoring_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/monitoring_page.py rename to scripts/tui/src/ovirt/node/setup/monitoring_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/network_page.py b/scripts/tui/src/ovirt/node/setup/network_page.py similarity index 89% rename from scripts/tui/src/ovirt/node/plugins/setup/network_page.py rename to scripts/tui/src/ovirt/node/setup/network_page.py index 0ef7954..d1f625f 100644 --- a/scripts/tui/src/ovirt/node/plugins/setup/network_page.py +++ b/scripts/tui/src/ovirt/node/setup/network_page.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# status.py - Copyright (C) 2012 Red Hat, Inc. +# network_page.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ import ovirt.node.ui import ovirt.node.utils.network import ovirt.node.config.network +from ovirt.node.config import defaults class Plugin(ovirt.node.plugins.NodePlugin): @@ -50,11 +51,11 @@ def model(self): # Pull name-/timeservers from config files (not defaults) - nameservers = ovirt.node.config.network.nameservers() + nameservers = dict(defaults.Nameservers().retrieve())["servers"] for idx, nameserver in enumerate(nameservers): self._model["dns[%d]" % idx] = nameserver - timeservers = ovirt.node.config.network.timeservers() + timeservers = dict(defaults.Timeservers().retrieve())["servers"] for idx, timeserver in enumerate(timeservers): self._model["ntp[%d]" % idx] = timeserver @@ -210,23 +211,27 @@ self.logger.info("effc %s" % effective_changes) self.logger.info("allc %s" % changes) - if "dns[0]" in effective_changes or \ - "dns[1]" in effective_changes: - new_servers = [v for k, v in effective_model \ - if k.startswith("dns[")] - self.logger.info("Setting new nameservers: %s" % new_servers) + nameservers = [] + for key in ["dns[0]", "dns[1]"]: + if key in effective_changes: + nameservers.append(effective_changes[key]) + if nameservers: + self.logger.info("Setting new nameservers: %s" % nameservers) model = ovirt.node.config.defaults.Nameservers() - model.configure(new_servers) + model.update(nameservers) - if "ntp[0]" in effective_changes or \ - "ntp[1]" in effective_changes: - new_servers = [v for k, v in effective_model \ - if k.startswith("ntp[")] - self.logger.info("Setting new timeservers: %s" % new_servers) + timeservers = [] + for key in ["ntp[0]", "ntp[1]"]: + if key in effective_changes: + timeservers.append(effective_changes[key]) + if timeservers: + self.logger.info("Setting new timeservers: %s" % timeservers) model = ovirt.node.config.defaults.Timeservers() - model.configure(new_servers) + model.update(timeservers) - if "nics" in changes: + if "nics" in changes and len(changes) == 1: iface = changes["nics"] self.logger.debug("Opening NIC Details dialog for '%s'" % iface) - return self._build_nic_details_dialog() \ No newline at end of file + return self._build_nic_details_dialog() + + return True \ No newline at end of file diff --git a/scripts/tui/src/ovirt/node/plugins/setup/ping.py b/scripts/tui/src/ovirt/node/setup/ping.py similarity index 98% rename from scripts/tui/src/ovirt/node/plugins/setup/ping.py rename to scripts/tui/src/ovirt/node/setup/ping.py index aaa4717..7a13c7d 100644 --- a/scripts/tui/src/ovirt/node/plugins/setup/ping.py +++ b/scripts/tui/src/ovirt/node/setup/ping.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# ping.py - Copyright (C) 2012 Red Hat, Inc. +# ping_page.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify diff --git a/scripts/tui/src/ovirt/node/plugins/setup/remote_storage_page.py b/scripts/tui/src/ovirt/node/setup/remote_storage_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/remote_storage_page.py rename to scripts/tui/src/ovirt/node/setup/remote_storage_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/security_page.py b/scripts/tui/src/ovirt/node/setup/security_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/security_page.py rename to scripts/tui/src/ovirt/node/setup/security_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/snmp_page.py b/scripts/tui/src/ovirt/node/setup/snmp_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/snmp_page.py rename to scripts/tui/src/ovirt/node/setup/snmp_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/status_page.py b/scripts/tui/src/ovirt/node/setup/status_page.py similarity index 98% rename from scripts/tui/src/ovirt/node/plugins/setup/status_page.py rename to scripts/tui/src/ovirt/node/setup/status_page.py index 667f3b8..14c2860 100644 --- a/scripts/tui/src/ovirt/node/plugins/setup/status_page.py +++ b/scripts/tui/src/ovirt/node/setup/status_page.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# status.py - Copyright (C) 2012 Red Hat, Inc. +# status_page.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify diff --git a/scripts/tui/src/ovirt/node/plugins/setup/support_page.py b/scripts/tui/src/ovirt/node/setup/support_page.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/support_page.py rename to scripts/tui/src/ovirt/node/setup/support_page.py diff --git a/scripts/tui/src/ovirt/node/plugins/setup/usage.py b/scripts/tui/src/ovirt/node/setup/usage.py similarity index 100% rename from scripts/tui/src/ovirt/node/plugins/setup/usage.py rename to scripts/tui/src/ovirt/node/setup/usage.py diff --git a/scripts/tui/src/ovirt/node/ui/__init__.py b/scripts/tui/src/ovirt/node/ui/__init__.py index 4aefe8f..0be7d37 100644 --- a/scripts/tui/src/ovirt/node/ui/__init__.py +++ b/scripts/tui/src/ovirt/node/ui/__init__.py @@ -61,7 +61,8 @@ self._signal_cbs = {} if name not in self._signal_cbs: self._signal_cbs[name] = [] - self.logger.debug("Registered new signal '%s' for '%s'" % (name, self)) + self.logger.debug("Registered new signal '%s' for '%s'" % (name, + self)) def connect_signal(self, name, cb): """Connect an callback to a signal @@ -257,8 +258,8 @@ label: Caption of this checkbox state: The initial change """ - def __init__(self, label, state=False): - super(Checkbox, self).__init__() + def __init__(self, label, state=False, is_enabled=True): + super(Checkbox, self).__init__(label, is_enabled) self.label = label self.state(state) @@ -347,4 +348,4 @@ self._hotkeys[str(hotkey)] = cb def run(self): - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/scripts/tui/src/ovirt/node/ui/builder.py b/scripts/tui/src/ovirt/node/ui/builder.py index 8129720..7573258 100644 --- a/scripts/tui/src/ovirt/node/ui/builder.py +++ b/scripts/tui/src/ovirt/node/ui/builder.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# tui.py - Copyright (C) 2012 Red Hat, Inc. +# builder.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -185,8 +185,8 @@ def on_widget_click_cb(widget, data=None): LOGGER.debug("Button click: %s %s" % (path, widget)) -# if type(item) is ovirt.node.ui.SaveButton: - plugin._on_ui_change({path: True}) + if type(item) is ovirt.node.ui.Button: + plugin._on_ui_change({path: True}) r = plugin._on_ui_save() parse_plugin_result(tui, plugin, r) diff --git a/scripts/tui/src/ovirt/node/ui/tui.py b/scripts/tui/src/ovirt/node/ui/tui.py index 043233b..21791fb 100644 --- a/scripts/tui/src/ovirt/node/ui/tui.py +++ b/scripts/tui/src/ovirt/node/ui/tui.py @@ -123,6 +123,16 @@ return urwid.AttrMap(screen, "screen") def display_plugin(self, plugin): + if self._check_outstanding_changes(): + return + timer = timeit.Timer() + self._current_plugin = plugin + page = ovirt.node.ui.builder.page_from_plugin(self, plugin) + self.display_page(page) + LOGGER.debug("Build and displayed page in %ss" % timer.timeit()) + + def _check_outstanding_changes(self): + has_outstanding_changes = False if self._current_plugin: pending_changes = self._current_plugin.pending_changes() if pending_changes: @@ -131,18 +141,14 @@ widgets = dict(self._current_plugin.ui_content().children) LOGGER.debug("Available widgets: %s" % widgets) for path, value in pending_changes.items(): - field = widgets[path].name - msg += "- %s\n" % (field.strip(":")) + if path in widgets: + field = widgets[path].name + msg += "- %s\n" % (field.strip(":")) self.display_dialog(urwid.Filler(urwid.Text( "The following fields were changed:\n%s" % msg)), "Pending changes") - return - - timer = timeit.Timer() - self._current_plugin = plugin - page = ovirt.node.ui.builder.page_from_plugin(self, plugin) - self.display_page(page) - LOGGER.debug("Build and displayed page in %ss" % timer.timeit()) + has_outstanding_changes = True + return has_outstanding_changes def display_page(self, page): LOGGER.debug("Displaying page %s" % page) diff --git a/scripts/tui/src/ovirt/node/utils/fs.py b/scripts/tui/src/ovirt/node/utils/fs.py index fd72aed..e6f1f83 100644 --- a/scripts/tui/src/ovirt/node/utils/fs.py +++ b/scripts/tui/src/ovirt/node/utils/fs.py @@ -26,7 +26,7 @@ import shutil import os from ovirt.node.utils import checksum, is_bind_mount -from ovirt.node.utils.process import system +from process import system LOGGER = logging.getLogger(__name__) @@ -43,6 +43,7 @@ contents = f.read() return contents + def copy_contents(src, dst): assert all([os.path.isfile(f) for f in [src, dst]]), \ "Source and destination need to exist" diff --git a/scripts/tui/src/ovirt/node/utils/security.py b/scripts/tui/src/ovirt/node/utils/security.py index a368b16..5be7359 100644 --- a/scripts/tui/src/ovirt/node/utils/security.py +++ b/scripts/tui/src/ovirt/node/utils/security.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# virt.py - Copyright (C) 2012 Red Hat, Inc. +# security.py - Copyright (C) 2012 Red Hat, Inc. # Written by Fabian Deutsch <[email protected]> # # This program is free software; you can redistribute it and/or modify @@ -24,7 +24,7 @@ import os.path -from . import process +import process def get_ssh_hostkey(variant="rsa"): diff --git a/scripts/tui/src/ovirt/node/valid.py b/scripts/tui/src/ovirt/node/valid.py index 5c21dd0..06f3c90 100644 --- a/scripts/tui/src/ovirt/node/valid.py +++ b/scripts/tui/src/ovirt/node/valid.py @@ -23,9 +23,10 @@ """ import re import socket +import urlparse -from . import base -from . import exceptions +import base +import exceptions class Validator(base.Base): @@ -302,3 +303,22 @@ def validate(self, value): return value == "" + + +class URL(Validator): + description = "a valid URL" + + requires_scheme = False + requires_netloc = False + requires_path = True + + def validate(self, value): + p = urlparse.urlparse(value) + is_valid = True + if self.requires_scheme: + is_valid &= p.scheme != "" + if self.requires_netloc: + is_valid &= p.netloc != "" + if self.requires_path: + is_valid &= p.path != "" + return is_valid -- To view, visit http://gerrit.ovirt.org/9932 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I684d85f1689ab97d22e5be4b5c740676751464c1 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-node Gerrit-Branch: master Gerrit-Owner: Fabian Deutsch <[email protected]> _______________________________________________ node-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/node-patches
