[node-patches] Change in ovirt-node[master]: installer: Add basic pages for reworked installer

fabiand at fedoraproject.org fabiand at fedoraproject.org
Wed Jan 9 20:39:49 UTC 2013


Fabian Deutsch has uploaded a new change for review.

Change subject: installer: Add basic pages for reworked installer
......................................................................

installer: Add basic pages for reworked installer

This patch contains much stuff to allow to build a page based installer.
This includes smaller changes on the curretn TUI code base, many new
pages for the installer and several fixes here and there.

Change-Id: I14a11475c715afbc5815e0573a35cf96e5b90434
Signed-off-by: Fabian Deutsch <fabiand at fedoraproject.org>
---
M scripts/tui/src/ovirt/node/app.py
A scripts/tui/src/ovirt/node/installer/__main__.py
A scripts/tui/src/ovirt/node/installer/boot_device_page.py
A scripts/tui/src/ovirt/node/installer/installation_device_page.py
A scripts/tui/src/ovirt/node/installer/keyboard_page.py
A scripts/tui/src/ovirt/node/installer/password_page.py
A scripts/tui/src/ovirt/node/installer/progress_page.py
A scripts/tui/src/ovirt/node/installer/welcome_page.py
M scripts/tui/src/ovirt/node/setup/network_page.py
M scripts/tui/src/ovirt/node/setup/ping.py
M scripts/tui/src/ovirt/node/setup/support_page.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/__init__.py
M scripts/tui/src/ovirt/node/utils/storage.py
M scripts/tui/src/ovirt/node/utils/system.py
17 files changed, 913 insertions(+), 145 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/37/10837/1

diff --git a/scripts/tui/src/ovirt/node/app.py b/scripts/tui/src/ovirt/node/app.py
index f12284b..993a56a 100644
--- a/scripts/tui/src/ovirt/node/app.py
+++ b/scripts/tui/src/ovirt/node/app.py
@@ -25,9 +25,13 @@
 which communicate with each other.
 """
 
+from ovirt.node import base, utils, plugins
+from ovirt.node.config import defaults
+from ovirt.node.utils import system
 import argparse
 import logging
 import logging.config
+import ovirt.node.ui.tui
 
 LOGGING = {
     'version': 1,
@@ -58,31 +62,25 @@
         },
     },
     'loggers': {
-        'ovirt.node': {
+        'ovirt': {
             'handlers': ['debug'],
-            'propagate': True,
             'level': 'DEBUG',
         },
         'ovirt.node': {
             'handlers': ['file'],
-            'level': 'INFO',
-            'propagate': True,
+            'level': 'DEBUG',
         },
     }
 }
+
 logging.config.dictConfig(LOGGING)
 #logging.basicConfig(level=logging.DEBUG,
 #                    filename="/tmp/app.log", filemode="w",
 #                    format="%(asctime)s %(levelname)s %(name)s %(message)s")
 
 
-import ovirt.node.ui.tui
-from ovirt.node import base, utils, plugins
-from ovirt.node.config import defaults
-
-
 class Application(base.Base):
-    plugins = []
+    __plugins = {}
 
     ui = None
 
@@ -118,19 +116,43 @@
 
         self.logger.debug("Commandline arguments: %s" % self.args)
 
+    def plugins(self):
+        return self.__plugins
+
     def __load_plugins(self):
-        self.plugins = []
+        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)
+                self.logger.debug("Registering plugin '%s': %s" %
+                                  (plugin.name(), plugin))
+                self.__plugins[plugin.name()] = plugin
             else:
                 self.logger.debug("Found no plugin in module: %s" % m)
 
-        for plugin in self.plugins:
+        for plugin in self.__plugins.values():
             self.logger.debug("Loading plugin %s" % plugin)
             self.ui.register_plugin(plugin.ui_name(), plugin)
+
+    def get_plugin(self, mixed):
+        """Find a plugin by name or type
+        """
+        mtype = type(mixed)
+        self.logger.debug("Looking up plugin: %s (%s)" % (mixed, mtype))
+        plugin = None
+
+        if isinstance(mixed, plugins.NodePlugin):
+            plugin = mixed
+        elif mtype in [str, unicode]:
+            plugin = self.__plugins[mixed]
+        elif mtype is type:
+            plugin = {type(p): p for p in self.__plugins}[mixed]
+        else:
+            raise Exception("Can't look up: %s" % mixed)
+
+        self.logger.debug("Found plugin for type: %s" % plugin)
+        return plugin
 
     def __drop_to_shell(self):
         with self.ui.suspended():
@@ -144,25 +166,23 @@
 
     def model(self, plugin_name):
         model = None
-        for plugin in self.plugins:
+        for plugin in self.__plugins:
             if plugin.name() == plugin_name:
                 model = plugin.model()
         return model
 
     @property
     def product(self):
-        return utils.system.ProductInformation()
+        return system.ProductInformation()
 
     def run(self):
         self.__load_plugins()
-        if not self.plugins:
+        if not self.__plugins:
             raise Exception("No plugins found in '%s'" % self.plugin_base)
         self.ui.register_hotkey("f2", self.__drop_to_shell)
         self.ui.register_hotkey("window resize", self.__check_terminal_size)
 
-        p = self.product
-        self.ui.header = "\n %s %s-%s\n" % (p.PRODUCT_SHORT, p.VERSION,
-                                          p.RELEASE)
+        self.ui.header = "\n %s\n" % str(self.product)
         self.ui.footer = "Press esc to quit."
         self.ui.run()
 
diff --git a/scripts/tui/src/ovirt/node/installer/__main__.py b/scripts/tui/src/ovirt/node/installer/__main__.py
new file mode 100644
index 0000000..c75aee8
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/__main__.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# ovirt-config-installer.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 setup application instance an start it.
+"""
+
+from ovirt.node import app, installer, plugins
+
+
+if __name__ == '__main__':
+    app = app.Application(installer)
+    app.run()
+
+
+class Plugin(plugins.NodePlugin):
+    transactions = None
+
+    def __init__(self, application):
+        super(Plugin, self).__init__(application)
+        application.ui.with_menu = False
+
+    def name(self):
+        return "installer"
+
+    def has_ui(self):
+        return False
diff --git a/scripts/tui/src/ovirt/node/installer/boot_device_page.py b/scripts/tui/src/ovirt/node/installer/boot_device_page.py
new file mode 100644
index 0000000..ec3585f
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/boot_device_page.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# boot_device_page.py - Copyright (C) 2013 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.
+
+"""
+Boot device selection page of the installer
+"""
+from ovirt.node import plugins, ui, utils
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
+    def name(self):
+        return "Boot Device"
+
+    def rank(self):
+        return 30
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        page_title = "Please select the disk to use for booting %s" % \
+                     self.application.product.PRODUCT_SHORT
+
+        widgets = [
+            ("header", ui.Header(page_title)),
+            ("button.next", ui.Table("",
+                                     " %6s  %11s  %5s" %
+                                     ("Location", "Device Name", "Size"),
+                                     self._device_list())),
+            ("label.details", DeviceDetails("(No device)"))
+        ]
+
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = [("button.quit", ui.Button("Quit")),
+                        ("button.back", ui.Button("Back")),
+                        ("button.next", ui.Button("Continue"))]
+        return page
+
+    def _device_list(self):
+        devices = utils.storage.Devices(fake=True)
+        all_devices = devices.get_all().items()
+        return [
+                (name, " %6s  %11s  %5s GB" % (d.bus, d.name, d.size))
+                for name, d in all_devices
+                ]
+
+    def on_change(self, changes):
+        if "button.next" in changes:
+            self._widgets["label.details"].set_device(changes["button.next"])
+
+    def on_merge(self, effective_changes):
+        if "button.next" in effective_changes:
+            self.transaction = "a"
+            self.application.ui.navigate.to_next_plugin()
+
+
+class DeviceDetails(ui.Label):
+    def set_device(self, device):
+        devices = utils.storage.Devices(fake=True)
+        all_devices = devices.get_all()
+        if not all_devices:
+            pass
+        """lines = [("Disk Details",),
+                 ("Device", ""),
+                 ("Model", ""),
+                 ("Bus Type", ""),
+                 ("Serial", ""),
+                 ("Size", ""),
+                 ("Description", ""),
+                 ]"""
+        self.set_text("%s" % device)
diff --git a/scripts/tui/src/ovirt/node/installer/installation_device_page.py b/scripts/tui/src/ovirt/node/installer/installation_device_page.py
new file mode 100644
index 0000000..4ecd1eb
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/installation_device_page.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# installation_devce_page.py - Copyright (C) 2013 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.
+
+"""
+Installation device selection page of the installer
+"""
+from ovirt.node import plugins, ui, utils
+from ovirt.node.installer.boot_device_page import DeviceDetails
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
+    def name(self):
+        return "Data Device"
+
+    def rank(self):
+        return 40
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        page_title = "Please select the disk(s) to use for installation " \
+                     "of %s" % self.application.product.PRODUCT_SHORT
+
+        widgets = [
+            ("header", ui.Header(page_title)),
+            ("button.next", ui.Table("",
+                                     " %6s  %11s  %5s" %
+                                     ("Location", "Device Name", "Size"),
+                                     self._device_list())),
+            ("label.details", DeviceDetails("(No device)"))
+        ]
+
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = [("button.quit", ui.Button("Quit")),
+                        ("button.back", ui.Button("Back")),
+                        ("button.next", ui.Button("Continue"))]
+        return page
+
+    def _device_list(self):
+        devices = utils.storage.Devices(fake=True)
+        all_devices = devices.get_all().items()
+        return [
+                (name, " %6s  %11s  %5s GB" % (d.bus, d.name, d.size))
+                for name, d in all_devices
+                ]
+
+    def on_change(self, changes):
+        if "button.next" in changes:
+            self._widgets["label.details"].set_device(changes["button.next"])
+
+    def on_merge(self, effective_changes):
+        if "button.next" in effective_changes:
+            self.transaction = "a"
+            self.application.ui.navigate.to_next_plugin()
diff --git a/scripts/tui/src/ovirt/node/installer/keyboard_page.py b/scripts/tui/src/ovirt/node/installer/keyboard_page.py
new file mode 100644
index 0000000..37edccd
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/keyboard_page.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# keyboard_page.py - Copyright (C) 2013 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.
+
+"""
+Keyboard page of the installer
+"""
+from ovirt.node import plugins, ui, utils
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
+    def name(self):
+        return "Keyboard"
+
+    def rank(self):
+        return 20
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        kbd = utils.Keyboard()
+        widgets = [
+            ("layout._header",
+                ui.Header("Keyboard Layout Selection")),
+            ("button.next", ui.Table("Available Keyboard Layouts",
+                                "", kbd.available_layouts())),
+        ]
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = [("button.quit", ui.Button("Quit")),
+                        ("button.next", ui.Button("Continue"))]
+        return page
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, effective_changes):
+        if "button.next" in effective_changes:
+            self.transaction = "a"
+            self.application.ui.navigate.to_next_plugin()
diff --git a/scripts/tui/src/ovirt/node/installer/password_page.py b/scripts/tui/src/ovirt/node/installer/password_page.py
new file mode 100644
index 0000000..b5f2138
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/password_page.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# password_page.py - Copyright (C) 2013 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.
+
+"""
+Password page of the installer
+"""
+from ovirt.node import plugins, ui
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
+    def name(self):
+        return "Console Password"
+
+    def rank(self):
+        return 50
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        widgets = [
+            ("layout._header",
+             ui.Header("Require a password for local console access?")),
+
+            ("divider[0]", ui.Divider()),
+            ("password", ui.PasswordEntry("Password:")),
+            ("password_confirmation", ui.PasswordEntry("Confirm Password:")),
+        ]
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = [("button.quit", ui.Button("Quit")),
+                        ("button.back", ui.Button("Back")),
+                        ("button.next", ui.Button("Install"))]
+        return page
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, effective_changes):
+        if "button.next" in effective_changes:
+            self.transaction = "a"
+            self.application.ui.navigate.to_next_plugin()
diff --git a/scripts/tui/src/ovirt/node/installer/progress_page.py b/scripts/tui/src/ovirt/node/installer/progress_page.py
new file mode 100644
index 0000000..9f77e0f
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/progress_page.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# progress_page.py - Copyright (C) 2013 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.
+from ovirt.node import plugins, ui, utils
+import threading
+import time
+
+"""
+Progress page of the installer
+"""
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+    _worker = None
+
+    def __init__(self, application):
+        super(Plugin, self).__init__(application)
+        self._worker = InstallerThread(self)
+
+    def name(self):
+        return "Installation Progress"
+
+    def rank(self):
+        return 60
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        widgets = [
+            ("layout._header",
+             ui.Header("%s is beeing installed ..." %
+                       self.application.product.PRODUCT_SHORT)),
+            ("divider[0]", ui.Divider()),
+            ("progressbar", ui.ProgressBar(0)),
+            ("divider[1]", ui.Divider()),
+            ("log", ui.Label("")),
+        ]
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = []
+        self._worker.start()
+        return page
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, effective_changes):
+        pass
+
+
+class InstallerThread(threading.Thread):
+    def __init__(self, progress_plugin):
+        super(InstallerThread, self).__init__()
+        self.progress_plugin = progress_plugin
+
+    @property
+    def logger(self):
+        return self.progress_plugin.logger
+
+    def run(self):
+        time.sleep(0.3)  # Give the UI some time to build
+        config = self.collect_config()
+        transaction = self.build_transaction_for(config)
+
+        progressbar = self.progress_plugin._widgets["progressbar"]
+        log = self.progress_plugin._widgets["log"]
+
+        txlen = len(transaction)
+        try:
+            for idx, tx_element in transaction.step():
+                idx += 1
+                self.logger.debug("Running %s: %s" % (idx, tx_element))
+                tx_element.commit()
+                percent = int(100.0 / txlen * idx)
+                progressbar.current(percent)
+                log.text("(%s/%s) %s" % (idx, txlen, tx_element.title))
+        except Exception as e:
+            log.text("EXECPTION: %s" % e)
+
+    def collect_config(self):
+        config = {}
+        app = self.progress_plugin.application
+        for pname, plugin in app.plugins().items():
+            self.logger.debug("Config for %s" % (pname))
+            try:
+                model = plugin.model()
+                config.update(model)
+                self.logger.debug("Merged config: %s" % (model))
+            except NotImplementedError:
+                self.logger.debug("Merged no config.")
+        return config
+
+    def build_transaction_for(self, config):
+        tx = utils.Transaction("Installation")
+        logger = self.logger
+        K = 10
+        for n in range(1, K):
+
+            class RE(utils.Transaction.Element):
+                title = "te %s" % n
+
+                def prepare(self):
+                    logger.debug("Preparing te: %s" % n)
+
+                def commit(self):
+                    time.sleep(1)
+
+            tx.append(RE())
+        return tx
diff --git a/scripts/tui/src/ovirt/node/installer/welcome_page.py b/scripts/tui/src/ovirt/node/installer/welcome_page.py
new file mode 100644
index 0000000..121f846
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/installer/welcome_page.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# welcome_page.py - Copyright (C) 2013 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.
+
+"""
+Welcome page of the installer
+
+The idea is that every page modifies it's own model, which is at the end pulled
+using the plugin.model() call and used to build a stream of
+transaction_elements (see progress_page.py)
+
+NOTE: Each page stores the information in the config page
+NOTE II: Or shall we build the transactions per page?
+"""
+from ovirt.node import plugins, ui
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    _widgets = None
+
+    def name(self):
+        return "Welcome"
+
+    def rank(self):
+        return 10
+
+    def model(self):
+        return self._model or {}
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        widgets = [
+            ("button.install", ui.Button("Install %s" %
+                                      str(self.application.product))),
+        ]
+        self._widgets = dict(widgets)
+        page = ui.Page(widgets)
+        page.buttons = [("button.quit", ui.Button("Quit"))]
+        return page
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, effective_changes):
+        if "button.install" in effective_changes:
+            self.transaction = "a"
+            self.application.ui.navigate.to_next_plugin()
diff --git a/scripts/tui/src/ovirt/node/setup/network_page.py b/scripts/tui/src/ovirt/node/setup/network_page.py
index 480c545..58f97be 100644
--- a/scripts/tui/src/ovirt/node/setup/network_page.py
+++ b/scripts/tui/src/ovirt/node/setup/network_page.py
@@ -22,6 +22,7 @@
 from ovirt.node.config import defaults
 from ovirt.node.plugins import Changeset
 from ovirt.node.utils import network
+import ovirt.node.setup.ping
 """
 Network page plugin
 """
@@ -117,6 +118,8 @@
             ("nics", ui.Table("Available System NICs",
                         "Device   Status         Model          MAC Address",
                         self._get_nics())),
+
+            ("button.ping", ui.Button("Ping")),
         ]
         # Save it "locally" as a dict, for better accessability
         self._widgets.update(dict(widgets))
@@ -283,6 +286,12 @@
             self.logger.debug("Save and close NIC")
             self._nic_dialog.close()
 
+        if "button.ping" in changes:
+            self.logger.debug("Opening ping page")
+            plugin_type = ovirt.node.setup.ping.Plugin
+            self.application.ui.navigate.to_plugin(plugin_type)
+            return
+
         # This object will contain all transaction elements to be executed
         txs = utils.Transaction("DNS and NTP configuration")
 
diff --git a/scripts/tui/src/ovirt/node/setup/ping.py b/scripts/tui/src/ovirt/node/setup/ping.py
index 5c7246e..b791f4e 100644
--- a/scripts/tui/src/ovirt/node/setup/ping.py
+++ b/scripts/tui/src/ovirt/node/setup/ping.py
@@ -34,11 +34,14 @@
     _widgets = None
 
     def name(self):
-        return "Tools (ping)"
+        return "Networking/Ping"
 
     def rank(self):
         return 999
 
+    def has_ui(self):
+        return False
+
     def model(self):
         """Returns the model of this plugin
         This is expected to parse files and all stuff to build up the model.
diff --git a/scripts/tui/src/ovirt/node/setup/support_page.py b/scripts/tui/src/ovirt/node/setup/support_page.py
index bd118d1..82bffdf 100644
--- a/scripts/tui/src/ovirt/node/setup/support_page.py
+++ b/scripts/tui/src/ovirt/node/setup/support_page.py
@@ -18,36 +18,47 @@
 # 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.
+from ovirt.node.plugins import NodePlugin
+from ovirt.node import ui
 
 """
 A plugin for a support page
 """
 
-import ovirt.node.plugins
-import ovirt.node.ui
 
-
-class Plugin(ovirt.node.plugins.NodePlugin):
+class Plugin(NodePlugin):
     def __init__(self, application):
         # Register F8: Display this plugin when F( is pressed
-        show_page = lambda: application.ui.show_page(self.ui_content())
-        application.ui.register_hotkey(["f8"], show_page)
+        show_plugin = lambda: application.ui.switch_to_plugin(self)
+        application.ui.register_hotkey(["f8"], show_plugin)
+        super(Plugin, self).__init__(application)
 
     def name(self):
         return "Support"
 
-    rank = lambda self: 999
+    def rank(self):
+        return 999
 
-    has_ui = lambda self: False
+    def has_ui(self):
+        return False
 
     def ui_content(self):
         widgets = [
-            ("features.info", ovirt.node.ui.Label("FIXME Support info"))
+            ("features.info", ui.Label("FIXME Support info"))
         ]
 
-        page = ovirt.node.ui.Page(widgets)
+        page = ui.Page(widgets)
         page.buttons = []
         return page
 
     def model(self):
         return {}
+
+    def validators(self):
+        return {}
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, changes):
+        pass
diff --git a/scripts/tui/src/ovirt/node/ui/__init__.py b/scripts/tui/src/ovirt/node/ui/__init__.py
index 3e42b17..0af2e72 100644
--- a/scripts/tui/src/ovirt/node/ui/__init__.py
+++ b/scripts/tui/src/ovirt/node/ui/__init__.py
@@ -19,7 +19,6 @@
 # MA  02110-1301, USA.  A copy of the GNU General Public License is
 # also available at http://www.gnu.org/copyleft/gpl.html.
 from ovirt.node import base
-import traceback
 
 """
 This contains abstract UI Elements
@@ -88,6 +87,116 @@
         if v:
             self.children = v
         return self.children
+
+
+class Window(Element):
+    """Abstract Window definition
+    """
+
+    app = None
+
+    def __init__(self, app):
+        super(Window, self).__init__()
+        self.logger.info("Creating UI for application '%s'" % app)
+        self.app = app
+
+        self._plugins = {}
+        self._hotkeys = {}
+
+        self.footer = None
+
+        self.navigate = Window.Navigation(self)
+
+    def register_plugin(self, title, plugin):
+        """Register a plugin to be shown in the UI
+        """
+        if title in self._plugins:
+            raise RuntimeError("Plugin with same name is " +
+                               "already registered: %s" % title)
+        self._plugins[title] = plugin
+
+    def register_hotkey(self, hotkey, cb):
+        """Register a hotkey
+        """
+        if type(hotkey) is str:
+            hotkey = [hotkey]
+        self.logger.debug("Registering hotkey '%s': %s" % (hotkey, cb))
+        self._hotkeys[str(hotkey)] = cb
+
+    def registered_plugins(self):
+        """Return a list of tuples of all registered plugins
+        """
+        return self._plugins.items()
+
+    def switch_to_plugin(self, plugin, check_for_changes=True):
+        """Show the given plugin
+        """
+        raise NotImplementedError
+
+    class Navigation(base.Base):
+        """A convenience class to navigate through a window
+        """
+
+        window = None
+        __current_plugin = None
+
+        def __init__(self, window):
+            self.window = window
+            super(Window.Navigation, self).__init__()
+
+        def index(self):
+            plugins = self.window.registered_plugins()
+            get_rank = lambda name_plugin: name_plugin[1].rank()
+            self.logger.debug("Available plugins: %s" % plugins)
+            sorted_plugins = [p for n, p in sorted(plugins, key=get_rank)
+                              if p.has_ui()]
+            self.logger.debug("Available plugins with ui: %s" % sorted_plugins)
+            return sorted_plugins
+
+        def to_plugin(self, plugin_candidate):
+            """Goes to the plugin (by instance or type)
+            Args
+                idx: The plugin instance/type to go to
+            """
+            self.logger.debug("Switching to plugin %s" % plugin_candidate)
+            plugin = self.window.app.get_plugin(plugin_candidate)
+            self.__current_plugin = plugin
+            self.window.switch_to_plugin(plugin, check_for_changes=False)
+            self.logger.debug("Switched to plugin %s" % plugin)
+
+        def to_nth(self, idx, is_relative=False):
+            """Goes to the plugin (by idx)
+            Any pending changes are ignored.
+
+            Args
+                idx: The plugin idx to go to
+            """
+            plugins = self.index()
+            self.logger.debug("Switching to page %s (%s)" % (idx, plugins))
+            if is_relative:
+                idx += plugins.index(self.__current_plugin)
+            plugin = plugins[idx]
+            self.to_plugin(plugin)
+
+        def to_next_plugin(self):
+            """Goes to the next plugin, based on the current one
+            """
+            self.to_nth(1, True)
+
+        def to_previous_plugin(self):
+            """Goes to the previous plugin, based on the current one
+            """
+            self.to_nth(-1, True)
+
+        def to_first_plugin(self):
+            """Goes to the first plugin
+            """
+            self.to_nth(0)
+
+        def to_last_plugin(self):
+            """Goes to the last plugin
+            """
+            self.to_nth(-1)
 
 
 class Page(ContainerElement):
@@ -189,6 +298,8 @@
 
 
 class Button(InputElement):
+    action = None
+
     def __init__(self, label, enabled=True):
         super(Button, self).__init__(label, enabled)
         self.text(label)
@@ -316,55 +427,6 @@
         return self._selected
 
 
-class Window(Element):
-    """Abstract Window definition
-    """
-
-    def __init__(self, app):
-        super(Window, self).__init__()
-        self.logger.info("Creating UI for application '%s'" % app)
-        self.app = app
-
-        self._plugins = {}
-        self._hotkeys = {}
-
-        self.footer = None
-
-    def register_plugin(self, title, plugin):
-        """Register a plugin to be shown in the UI
-        """
-        self._plugins[title] = plugin
-
-    def register_hotkey(self, hotkey, cb):
-        """Register a hotkey
-        """
-        if type(hotkey) is str:
-            hotkey = [hotkey]
-        self.logger.debug("Registering hotkey '%s': %s" % (hotkey, cb))
-        self._hotkeys[str(hotkey)] = cb
-
-    def show_dialog(self, dialog):
-        """Show a dialog.
-        The dialog can be closed using dialog.close()
-
-        Args:
-            dialog: The dialog to be shown
-        """
-        raise NotImplementedError
-
-    def show_page(self, page):
-        """Show / switch to a page.
-        Displays the given page (which does not need to be patr of a plugin)
-
-        Args:
-            page: The page to be shown
-        """
-        raise NotImplementedError
-
-    def run(self):
-        raise NotImplementedError
-
-
 class TransactionProgressDialog(Dialog):
     def __init__(self, transaction, plugin, initial_text=""):
         self.transaction = transaction
@@ -383,7 +445,7 @@
         self._progress_label.set_text("\n".join(self.texts))
 
     def run(self):
-        self.plugin.application.ui.show_dialog(self)
+        self.plugin.application.ui._show_dialog(self)
         self._close_button.enabled(False)
         if self.transaction:
             self.logger.debug("Initiating transaction")
@@ -394,16 +456,12 @@
 
     def __run_transaction(self):
         try:
-            self.logger.debug("Preparing transaction %s" % self.transaction)
-            self.transaction.prepare()  # Just to display something in dry mode
-            for idx, e in enumerate(self.transaction):
-                txt = "(%s/%s) %s" % (idx + 1, len(self.transaction), e.title)
+            for idx, tx_element in self.transaction.step():
+                txt = "(%s/%s) %s" % (idx + 1, len(self.transaction),
+                                      tx_element.title)
                 self.add_update(txt)
-                self.plugin.dry_or(lambda: e.commit())
+                self.plugin.dry_or(lambda: tx_element.commit())
             self.add_update("\nAll changes were applied successfully.")
         except Exception as e:
             self.add_update("\nAn error occurred while applying the changes:")
             self.add_update("%s" % e.message)
-            self.logger.warning("'%s' on transaction '%s': %s - %s" %
-                                (type(e), self.transaction, e, e.message))
-            self.logger.debug(str(traceback.format_exc()))
diff --git a/scripts/tui/src/ovirt/node/ui/builder.py b/scripts/tui/src/ovirt/node/ui/builder.py
index 0e1724b..87a2e8d 100644
--- a/scripts/tui/src/ovirt/node/ui/builder.py
+++ b/scripts/tui/src/ovirt/node/ui/builder.py
@@ -24,12 +24,11 @@
 Is based on the visitor pattern
 """
 
+from ovirt.node import ui
+import logging
+import ovirt.node.exceptions
 import urwid
 
-import logging
-
-import ovirt.node.exceptions
-from ovirt.node import ui
 
 LOGGER = logging.getLogger(__name__)
 
@@ -101,15 +100,25 @@
         ui.Row: build_row,
     }
 
+    item_type = None
+
     # Check if builder is available for UI Element
-    assert type(item) in item_to_builder, "No widget for item type"
+    if item_type in item_to_builder:
+        item_type = type(item)
+    else:
+        # It could be a derived type, therefor find it's base:
+        for sub_type in type(item).mro():
+            if sub_type in item_to_builder:
+                item_type = sub_type
+
+    assert item_type, "No widget for item type"
 
     # Build widget from UI Element
-    build_func = item_to_builder[type(item)]
+    build_func = item_to_builder[item_type]
     widget = build_func(path, item, tui, plugin)
 
     # Populate with values
-    if type(item) in [ui.Entry,
+    if item_type in [ui.Entry,
                       ui.PasswordEntry,
                       ui.Label,
                       ui.KeywordLabel,
@@ -172,7 +181,7 @@
                 widget.notice = e.message
                 widget.valid(False)
 
-        tui._draw_screen()
+#        tui._draw_screen()
     urwid.connect_signal(widget, 'change', on_widget_value_change)
 
     return widget
@@ -208,23 +217,30 @@
         plugin.sig_valid.connect(on_valid_cb)
         on_valid_cb(None, None)
 
+    if not item.action:
+        if itemtype in [ui.Button, ui.SaveButton]:
+            item.action = lambda: plugin._on_ui_save()
+        if itemtype in [ui.CloseButton]:
+            item.action = lambda: tui.close_topmost_dialog()
+        if itemtype in [ui.ResetButton]:
+            item.action = lambda: plugin._on_ui_reset()
+    else:
+        LOGGER.debug("Using custom callback for item " +
+                     "%s: %s" % (item, item.action.callback))
+
     def on_widget_click_cb(widget, data=None):
         LOGGER.debug("Button click: %s" % {"path": path, "widget": widget})
         if itemtype is ui.Button:
             plugin._on_ui_change({path: True})
-        if itemtype in [ui.Button, ui.SaveButton]:
-            r = plugin._on_ui_save()
-        if itemtype in [ui.CloseButton]:
-            r = tui.close_topmost_dialog()
+
+        r = item.action()
+
         if itemtype in [ui.ResetButton]:
-            r = plugin._on_ui_reset()
-            tui._display_plugin(plugin)
+            # Like a reload ...
+            tui.switch_to_plugin(plugin)
+
         parse_plugin_result(tui, plugin, r)
 
-#        else:
-#           Not propagating the signal as a signal to the plugin
-#           item.emit_signal("click", widget)
-#            plugin._on_ui_change({path: True})
     urwid.connect_signal(widget, "click", on_widget_click_cb)
 
     def on_item_enabled_change_cb(w, v):
@@ -317,11 +333,11 @@
 
         if type(result) in [ui.Page]:
             LOGGER.debug("Page requested.")
-            tui.show_page(result)
+            tui._show_page(result)
 
         elif type(result) in [ui.Dialog]:
             LOGGER.debug("Dialog requested.")
-            dialog = tui.show_dialog(result)
+            dialog = tui._show_dialog(result)
 
             def on_item_close_changed_cb(i, v):
                 dialog.close()
diff --git a/scripts/tui/src/ovirt/node/ui/tui.py b/scripts/tui/src/ovirt/node/ui/tui.py
index 8a2c60a..17218fd 100644
--- a/scripts/tui/src/ovirt/node/ui/tui.py
+++ b/scripts/tui/src/ovirt/node/ui/tui.py
@@ -23,13 +23,11 @@
 The urwid TUI base library
 """
 
-import time
-import urwid
-
-from ovirt.node import base
-from ovirt.node import ui
+from ovirt.node import base, ui
 import ovirt.node.ui.builder
 import ovirt.node.ui.widgets
+import time
+import urwid
 
 
 def inherits(obj, t):
@@ -53,6 +51,8 @@
 
     header = u"\n Configuration TUI\n"
     footer = u"Press ctrl+c to quit"
+
+    with_menu = True
 
     element_styles = {
         "text": "black",
@@ -91,9 +91,10 @@
                ('plugin.widget.button.disabled', element_styles["disabled"]),
                ('plugin.widget.label', element_styles["text"]),
                ('plugin.widget.label.keyword', element_styles["label"]),
-               ('plugin.widget.progressbar.box', 'light gray'),
+               ('plugin.widget.progressbar.box', element_styles["disabled"]),
                ('plugin.widget.progressbar.uncomplete', None),
-               ('plugin.widget.progressbar.complete', None, 'light gray'),
+               ('plugin.widget.progressbar.complete', "white",
+                element_styles["disabled"]),
                ('plugin.widget.options', element_styles["label"]),
                ('plugin.widget.options.label', element_styles["label"]),
                ('plugin.widget.dialog', None),
@@ -104,19 +105,34 @@
                ('plugin.widget.checkbox', element_styles["label"]),
                ]
 
-    def __init__(self, app):
+    def __init__(self, app, with_menu=True):
         super(UrwidTUI, self).__init__(app)
+        self.with_menu = with_menu
         self.logger.debug("Creating urwid tui for '%s'" % app)
         self.logger.debug("Detected encoding: %s" % urwid.get_encoding_mode())
 
-    def show_body(self, body):
+    def switch_to_plugin(self, plugin, check_for_changes=True):
+        self.logger.debug("Switching to plugin " +
+                          "%s, with checks? %s" % (plugin, check_for_changes))
+        if check_for_changes and self._check_outstanding_changes():
+            return
+        start = time.time()
+        self._current_plugin = plugin
+        plugin_page = ovirt.node.ui.builder.page_from_plugin(self, plugin)
+        self.__display_as_page(plugin_page)
+        stop = time.time()
+        diff = stop - start
+        self.logger.debug("Build and displayed plugin_page in %ss" %
+                          diff)
+
+    def _show_body(self, body):
         """
         """
         assert inherits(body, ui.Page)
         widget = ui.builder.build_page(self, self._current_plugin, body)
         self.__display_as_body(widget)
 
-    def show_page(self, page):
+    def _show_page(self, page):
         """Shows the ui.Page as a page.
         This transforms the abstract ui.Page to a urwid specififc version
         and displays it.
@@ -125,7 +141,7 @@
         widget = ui.builder.build_page(self, self._current_plugin, page)
         self.__display_as_page(widget)
 
-    def show_dialog(self, dialog):
+    def _show_dialog(self, dialog):
         """Shows the ui.Dialog as a dialog.
         This transforms the abstract ui.Dialog to a urwid specififc version
         and displays it.
@@ -165,25 +181,34 @@
         self.__loop = urwid.MainLoop(self.__main_frame,
                               self._convert_palette(),
                               input_filter=self.__filter_hotkeys)
+
+        self.navigate.to_first_plugin()
+
         self.__loop.run()
 
-    def __build_menu(self):
+    def __build_plugin_menu(self):
         self.__menu = ovirt.node.ui.widgets.PluginMenu(self._plugins)
 
         def menu_item_changed(plugin):
-            self._display_plugin(plugin)
+            self.switch_to_plugin(plugin)
         urwid.connect_signal(self.__menu, 'changed', menu_item_changed)
 
     def __create_screen(self):
-        self.__build_menu()
+        columns = []
+
         self.__page_frame = urwid.Frame(urwid.Filler(urwid.Text("")))
-        self.__menu.set_focus(0)
+
+        if self.with_menu:
+            self.__build_plugin_menu()
+            self.__menu.set_focus(0)
+            columns += [("weight", 0.3, self.__menu)]
 
         self.__notice = urwid.Text("Note: ")
         self.__notice_filler = urwid.Filler(self.__notice)
         self.__notice_attrmap = urwid.AttrMap(self.__notice_filler, "notice")
-        menu_frame_columns = urwid.Columns([("weight", 0.3, self.__menu),
-                              self.__page_frame], 4)
+        columns += [self.__page_frame]
+
+        menu_frame_columns = urwid.Columns(columns, 4)
 
         body = urwid.Pile([
 #                           ("fixed", 3, self.__notice_attrmap),
@@ -228,18 +253,6 @@
 #        filler = urwid.Filler(page, ("fixed top", 1), height=35)
         filler = urwid.Pile([page])
         self.__page_frame.body = filler
-
-    def _display_plugin(self, plugin):
-        if self._check_outstanding_changes():
-            return
-        start = time.time()
-        self._current_plugin = plugin
-        plugin_page = ovirt.node.ui.builder.page_from_plugin(self, plugin)
-        self.__display_as_page(plugin_page)
-        stop = time.time()
-        diff = stop - start
-        self.logger.debug("Build and displayed plugin_page in %ss" %
-                          diff)
 
     def __display_as_dialog(self, body, title, escape_key="esc"):
         self.logger.debug("Displaying dialog: %s / %s" % (body, title))
@@ -318,7 +331,7 @@
             if not hasattr(self, "_error_dialog") or not self._error_dialog:
                 d = ui.Dialog("Error", [("dialog.error", ui.Label(msg))])
                 d.buttons = []
-                self._error_dialog = self.show_dialog(d)
+                self._error_dialog = self._show_dialog(d)
         else:
             if hasattr(self, "_error_dialog") and self._error_dialog:
                 self._error_dialog.close()
diff --git a/scripts/tui/src/ovirt/node/utils/__init__.py b/scripts/tui/src/ovirt/node/utils/__init__.py
index 6c082bb..dad0e46 100644
--- a/scripts/tui/src/ovirt/node/utils/__init__.py
+++ b/scripts/tui/src/ovirt/node/utils/__init__.py
@@ -18,6 +18,11 @@
 # 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.
+from ovirt.node import base, exceptions
+import augeas as _augeas
+import hashlib
+import system_config_keyboard.keyboard
+import traceback
 
 """
 Utility functions.
@@ -26,11 +31,6 @@
 Use the .config package for stuff related to configuration files.
 And use the model.py module for oVirt Node's defaults file.
 """
-
-from ovirt.node import base, exceptions
-import augeas as _augeas
-import hashlib
-import system_config_keyboard.keyboard
 
 
 class AugeasWrapper(base.Base):
@@ -203,11 +203,18 @@
     Step B
     'Stepped B'
 
->>> tx = Transaction("Steps", [StepA(), StepB(), StepC()])
+    >>> tx = Transaction("Steps", [StepA(), StepB(), StepC()])
     >>> tx()
     Traceback (most recent call last):
         ...
     TransactionError: 'Transaction failed: Step C'
+
+    >>> txs = [
+    ...     Transaction("Step A", [StepA()]),
+    ...     Transaction("Step B", [StepB()])
+    ... ]
+    >>> txs
+    [[<StepA 'None'>], [<StepB 'None'>]]
     """
     def __init__(self, title, elements=[]):
         super(Transaction, self).__init__()
@@ -253,6 +260,23 @@
         self.logger.info("Transaction '%s' succeeded" % self)
         return True
 
+    def __str__(self):
+        return "<%s '%s' with %s>" % (self.__class__.__name__,
+                                      self.title, list.__str__(self))
+
+    def step(self):
+        try:
+            self.logger.debug("Preparing transaction %s" % self)
+            self.prepare()
+            for idx, e in enumerate(self):
+                yield (idx, e)
+        except Exception as e:
+            self.logger.warning("'%s' on transaction '%s': %s - %s" %
+                                (type(e), self, e, e.message))
+            self.logger.debug(str(traceback.format_exc()))
+            raise e
+        self.logger.debug("Finished transaction %s successfully" % self)
+
     class Element(base.Base):
         title = None
 
diff --git a/scripts/tui/src/ovirt/node/utils/storage.py b/scripts/tui/src/ovirt/node/utils/storage.py
index cf1b3a8..144a44d 100644
--- a/scripts/tui/src/ovirt/node/utils/storage.py
+++ b/scripts/tui/src/ovirt/node/utils/storage.py
@@ -20,6 +20,7 @@
 # also available at http://www.gnu.org/copyleft/gpl.html.
 
 from ovirt.node import base
+import os
 
 
 class iSCSI(base.Base):
@@ -43,3 +44,63 @@
         if domain:
             onet.set_nfsv4_domain(domain)
         return onet.get_current_nfsv4_domain()
+
+
+class Devices(base.Base):
+    """A class to retrieve available storage devices
+    """
+    def __init__(self, fake=False):
+        super(Devices, self).__init__()
+        if fake:
+            self._devices = {
+            "name": Device("bus", "name", "size", "desc", "serial", "model")
+            }
+        else:
+            import ovirtnode.storage
+            self._storage = ovirtnode.storage.Storage()
+
+    def get_all(self):
+        if self._devices:
+            return self._devices
+        from ovirtnode.ovirtfunctions import translate_multipath_device
+        dev_names, disk_dict = self._storage.get_udev_devices()
+        devices = {}
+        for dev in dev_names:
+            dev = translate_multipath_device(dev)
+            if dev in devices:
+                self.logger.warning("Device is already in dict: %s" % dev)
+                continue
+            if dev not in disk_dict:
+                self.logger.warning("Device in names but not in dict: " +
+                                    "%s" % dev)
+                continue
+            if dev == self.live_disk:
+                self.logger.info("Ignoring device " +
+                                 "%s it's the live media" % dev)
+                continue
+            infos = disk_dict[dev].split(",", 5)
+            device = Device(*infos)
+            device.name = os.path.basename(device.name).replace(" ", "")
+            device.name = translate_multipath_device(device.name)
+            if device.name in devices:
+                self.logger.debug("Device with same name already " +
+                                  "exists: %s" % device.name)
+            devices[device.name] = device
+
+
+class Device(base.Base):
+    """Wrapps the information about a udev storage device
+    """
+    bus = None
+    name = None
+    size = None
+    desc = None
+    serial = None
+    model = None
+
+    def __init__(self, bus, name, size, desc, serial, model):
+        super(Device, self).__init__()
+        vals = [bus, name, size, desc, serial, model]
+        props = ["bus", "name", "size", "desc", "serial", "model"]
+        for prop, val in zip(props, vals):
+            self.__dict__[prop] = val
diff --git a/scripts/tui/src/ovirt/node/utils/system.py b/scripts/tui/src/ovirt/node/utils/system.py
index 89cdd9e..86fb697 100644
--- a/scripts/tui/src/ovirt/node/utils/system.py
+++ b/scripts/tui/src/ovirt/node/utils/system.py
@@ -54,3 +54,6 @@
         self.PRODUCT_SHORT = augg("PRODUCT_SHORT") or "oVirt"
         self.VERSION = augg("VERSION")
         self.RELEASE = augg("RELEASE")
+
+    def __str__(self):
+        return "%s %s-%s" % (self.PRODUCT_SHORT, self.VERSION, self.RELEASE)


--
To view, visit http://gerrit.ovirt.org/10837
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I14a11475c715afbc5815e0573a35cf96e5b90434
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