From fa37cfcf560506f49bb00b9d216b1e7646a6905b Mon Sep 17 00:00:00 2001 From: Don Penney Date: Mon, 13 Nov 2017 17:21:05 -0500 Subject: [PATCH] TIS Progress and error handling --- data/tmux.conf | 3 +- pyanaconda/errors.py | 24 +++++++-- pyanaconda/flags.py | 1 + pyanaconda/install.py | 4 ++ pyanaconda/kickstart.py | 3 ++ pyanaconda/packaging/rpmostreepayload.py | 5 ++ pyanaconda/packaging/yumpayload.py | 15 +++++- pyanaconda/tisnotify.py | 91 ++++++++++++++++++++++++++++++++ pyanaconda/ui/gui/hubs/progress.py | 4 ++ pyanaconda/ui/tui/spokes/progress.py | 4 ++ 10 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 pyanaconda/tisnotify.py diff --git a/data/tmux.conf b/data/tmux.conf index 89f788b..7903b06 100644 --- a/data/tmux.conf +++ b/data/tmux.conf @@ -1,6 +1,7 @@ # tmux.conf for the anaconda environment bind -n M-tab next +bind -n C-o next bind -n F1 list-keys set-option -s exit-unattached off @@ -10,7 +11,7 @@ set-option -g history-limit 10000 new-session -s anaconda -n main "anaconda" -set-option status-right '#[fg=blue]#(echo -n "Switch tab: Alt+Tab | Help: F1 ")' +set-option status-right '#[fg=blue]#(echo -n "Switch: Alt+Tab or Ctrl-o ")' new-window -d -n shell "bash --login" new-window -d -n log "tail -F /tmp/anaconda.log" diff --git a/pyanaconda/errors.py b/pyanaconda/errors.py index 860b228..1d1d34b 100644 --- a/pyanaconda/errors.py +++ b/pyanaconda/errors.py @@ -19,6 +19,7 @@ # Author(s): Chris Lumens from pyanaconda.i18n import _ +from pyanaconda.tisnotify import tisnotify __all__ = ["ERROR_RAISE", "ERROR_CONTINUE", "ERROR_RETRY", "InvalidImageSizeError", "MissingImageError", "MediaUnmountError", @@ -81,6 +82,19 @@ ERROR_RAISE = 0 ERROR_CONTINUE = 1 ERROR_RETRY = 2 +# +# WRS: If a fatal error occurs in a %pre, anaconda hasn't setup the UI yet, +# and an exception occurs in the error handler. This is a basic dummy UI +# to avoid this exception and print the error message. +# +class DefaultUI(object): + def __init__(self): + pass + + def showError(self, msg): + print "\n\n", msg + + ### ### TOP-LEVEL ERROR HANDLING OBJECT ### @@ -304,12 +318,12 @@ class ErrorHandler(object): """ rc = ERROR_RAISE + # WRS: Notify the controller installation has failed + tisnotify.failed() + if not self.ui: - # While Pylint thinks something else, this should be likely OK - # for an exception handler. - # - # pylint: disable=misplaced-bare-raise - raise + # WRS: Use the basic UI + self.ui = DefaultUI() _map = {"PartitioningError": self._partitionErrorHandler, "FSResizeError": self._fsResizeHandler, diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py index 8a97f95..3d0d2da 100644 --- a/pyanaconda/flags.py +++ b/pyanaconda/flags.py @@ -71,6 +71,7 @@ class Flags(object): self.ksprompt = True self.rescue_mode = False self.kexec = False + self.tisNotifyPort = "0" # nosave options self.nosave_input_ks = False self.nosave_output_ks = False diff --git a/pyanaconda/install.py b/pyanaconda/install.py index 26e1b26..bd8f85b 100644 --- a/pyanaconda/install.py +++ b/pyanaconda/install.py @@ -35,6 +35,9 @@ from pyanaconda.ui.lib.entropy import wait_for_entropy from pyanaconda.kexec import setup_kexec from pyanaconda.kickstart import runPostScripts, runPreInstallScripts from pykickstart.constants import SNAPSHOT_WHEN_POST_INSTALL + +from pyanaconda.tisnotify import tisnotify + import logging import blivet log = logging.getLogger("anaconda") @@ -139,6 +142,7 @@ def doConfiguration(storage, payload, ksdata, instClass): with progress_report(N_("Creating snapshots")): ksdata.snapshot.execute(storage, ksdata, instClass) + tisnotify.installed() progress_complete() def doInstall(storage, payload, ksdata, instClass): diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index 50515c8..d95b2df 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -90,6 +90,8 @@ from pykickstart.sections import NullSection, PackageSection, PostScriptSection, from pykickstart.version import returnClassForVersion, RHEL7 from pykickstart.options import KSOptionParser +from pyanaconda.tisnotify import tisnotify + import logging log = logging.getLogger("anaconda") stderrLog = logging.getLogger("anaconda.stderr") @@ -2481,6 +2483,7 @@ def runPreScripts(scripts): if len(preScripts) == 0: return + tisnotify.preinstall() log.info("Running kickstart %%pre script(s)") stdoutLog.info(_("Running pre-installation scripts")) diff --git a/pyanaconda/packaging/rpmostreepayload.py b/pyanaconda/packaging/rpmostreepayload.py index 7cf59d7..8896ba1 100644 --- a/pyanaconda/packaging/rpmostreepayload.py +++ b/pyanaconda/packaging/rpmostreepayload.py @@ -36,6 +36,8 @@ from gi.repository import Gio from blivet.size import Size +from pyanaconda.tisnotify import tisnotify + import logging log = logging.getLogger("anaconda") @@ -69,6 +71,7 @@ class RPMOSTreePayload(ArchivePayload): """Like iutil.execWithRedirect, but treat errors as fatal""" rc = iutil.execWithRedirect(cmd, argv, **kwargs) if rc != 0: + tisnotify.failed() exn = PayloadInstallError("%s %s exited with code %d" % (cmd, argv, rc)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: raise exn @@ -183,6 +186,7 @@ class RPMOSTreePayload(ArchivePayload): GLib.Variant('a{sv}', pull_opts), progress, cancellable) except GLib.GError as e: + tisnotify.failed() exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: @@ -227,6 +231,7 @@ class RPMOSTreePayload(ArchivePayload): try: self._copyBootloaderData() except (OSError, RuntimeError) as e: + tisnotify.failed() exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: diff --git a/pyanaconda/packaging/yumpayload.py b/pyanaconda/packaging/yumpayload.py index c6aa234..a0497e0 100644 --- a/pyanaconda/packaging/yumpayload.py +++ b/pyanaconda/packaging/yumpayload.py @@ -46,6 +46,8 @@ from pyanaconda.simpleconfig import simple_replace from functools import wraps from urlgrabber.grabber import URLGrabber, URLGrabError +from pyanaconda.tisnotify import tisnotify + import logging log = logging.getLogger("packaging") @@ -181,6 +183,8 @@ class YumPayload(PackagePayload): # save repomd metadata self._repoMD_list = [] + self.tisNotifyPort = flags.cmdline.get("tisNotifyPort") + self.reset() def reset(self, root=None, releasever=None): @@ -1347,6 +1351,8 @@ reposdir=%s if self.data.packages.handleMissing == KS_MISSING_IGNORE: return + tisnotify.failed() + # If we're doing non-interactive ks install, raise CmdlineError, # otherwise the system will just reboot automatically if flags.automatedInstall and not flags.ksprompt: @@ -1524,6 +1530,7 @@ reposdir=%s try: self.checkSoftwareSelection() except DependencyError as e: + tisnotify.failed() if errorHandler.cb(e) == ERROR_RAISE: progressQ.send_quit(1) while True: @@ -1578,6 +1585,10 @@ reposdir=%s key, text = line.split(":", 1) msg = progress_map[key] + text progressQ.send_message(msg) + if line.startswith("PROGRESS_POST"): + tisnotify.postinstall() + elif not text.startswith(" error "): + tisnotify.installing(text) log.debug(msg) elif line.startswith("DEBUG:"): log.debug(line[6:]) @@ -1590,7 +1601,8 @@ reposdir=%s install_errors.append(line[6:]) else: log.debug(line) - except IOError as e: + except (IOError, OSError) as e: + tisnotify.failed() log.error("Error running anaconda-yum: %s", e) exn = PayloadInstallError(str(e)) if errorHandler.cb(exn) == ERROR_RAISE: @@ -1612,6 +1624,7 @@ reposdir=%s shutil.rmtree(iutil.getSysroot()+"/var/tmp/yum.cache") if install_errors: + tisnotify.failed() exn = PayloadInstallError("\n".join(install_errors)) if errorHandler.cb(exn) == ERROR_RAISE: progressQ.send_quit(1) diff --git a/pyanaconda/tisnotify.py b/pyanaconda/tisnotify.py new file mode 100644 index 0000000..bf5d9bd --- /dev/null +++ b/pyanaconda/tisnotify.py @@ -0,0 +1,91 @@ +""" +Copyright (c) 2016-2017 Wind River Systems, Inc. + SPDX-License-Identifier: Apache-2.0 + + + +""" + +import os +import re +import subprocess +import time + +from pyanaconda.flags import flags + +class TisNotify(): + + def __init__(self): + self.tisnotify = flags.cmdline.get("tisnotify") + self.regex = re.compile(r'\(([\d\/]*)\)$') + self.DEVNULL = open(os.devnull, "w") + self.last_installing = 0 + + def sendNotification(self, data): + try: + subprocess.call(['/usr/bin/curl', + '--data', data, + self.tisnotify], + stdout=self.DEVNULL, + stderr=self.DEVNULL) + except: + pass + + def preinstall(self): + if self.tisnotify is None: + return + + data = "install_state=preinstall" + self.sendNotification(data) + + def installing(self, text): + if self.tisnotify is None: + return + + match = self.regex.search(text) + if match is None: + return + + if (time.time() - self.last_installing) >= 5: + self.last_installing = time.time() + data = "install_state=installing&install_state_info=%s" % match.groups()[0] + self.sendNotification(data) + + def postinstall(self): + if self.tisnotify is None: + return + + data = "install_state=postinstall" + self.sendNotification(data) + + def installed(self): + if self.tisnotify is None: + return + + data = "install_state=installed" + self.sendNotification(data) + + def failed(self): + if self.tisnotify is None: + return + + data = "install_state=failed" + self.sendNotification(data) + + etc_dir = '/mnt/sysimage/etc' + platform_dir = etc_dir + '/platform' + failed_flag = platform_dir + '/platform/installation_failed' + motd_file = etc_dir + '/motd' + + # Set installation_failed flag, if possible and not already done + if os.path.exists(platform_dir) and not os.path.exists(failed_flag): + try: + subprocess.call(['touch', '/mnt/sysimage/etc/platform/installation_failed']) + with open(motd_file, 'a') as f: + f.write('Installation failure. Please check logs or reinstall.\n\n') + except: + pass + + +tisnotify = TisNotify() + diff --git a/pyanaconda/ui/gui/hubs/progress.py b/pyanaconda/ui/gui/hubs/progress.py index 0e4dbed..b342bd5 100644 --- a/pyanaconda/ui/gui/hubs/progress.py +++ b/pyanaconda/ui/gui/hubs/progress.py @@ -44,6 +44,8 @@ from pykickstart.constants import KS_SHUTDOWN, KS_REBOOT from pyanaconda.ui.gui.hubs import Hub from pyanaconda.ui.gui.utils import gtk_action_nowait, gtk_call_once +from pyanaconda.tisnotify import tisnotify + __all__ = ["ProgressHub"] class ProgressHub(Hub): @@ -124,6 +126,8 @@ class ProgressHub(Hub): # to indicate this method should be removed from the idle loop. return False elif code == progressQ.PROGRESS_CODE_QUIT: + if args[0] != 0: + tisnotify.failed() sys.exit(args[0]) q.task_done() diff --git a/pyanaconda/ui/tui/spokes/progress.py b/pyanaconda/ui/tui/spokes/progress.py index 1feeb08..8221e31 100644 --- a/pyanaconda/ui/tui/spokes/progress.py +++ b/pyanaconda/ui/tui/spokes/progress.py @@ -31,6 +31,8 @@ from pyanaconda.ui.tui.spokes import StandaloneTUISpoke from pyanaconda.ui.tui.hubs.summary import SummaryHub from pyanaconda.ui.tui.simpleline.base import ExitAllMainLoops +from pyanaconda.tisnotify import tisnotify + __all__ = ["ProgressSpoke"] class ProgressSpoke(StandaloneTUISpoke): @@ -101,6 +103,8 @@ class ProgressSpoke(StandaloneTUISpoke): print('') return True elif code == progressQ.PROGRESS_CODE_QUIT: + if args[0] != 0: + tisnotify.failed() sys.exit(args[0]) q.task_done() -- 1.8.3.1