[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