[node-patches] Change in ovirt-node[master]: defaults: Build objects around configuration

fabiand at fedoraproject.org fabiand at fedoraproject.org
Tue Dec 11 20:09:39 UTC 2012


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 <fabiand at fedoraproject.org>
---
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 <fabiand at redhat.com>
-#
-# 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 <fabiand at redhat.com>
 #
 # 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 = "root at host.example.com"
+    >>> n = KDump(cfgfile)
+    >>> n.update(nfs_url, ssh_url)
+    >>> n.retrieve()
+    [('nfs', 'host.example.com'), ('ssh', 'root at host.example.com')]
+    """
     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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at redhat.com>
 #
 # 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 <fabiand at fedoraproject.org>



More information about the node-patches mailing list