summaryrefslogtreecommitdiff
path: root/backport-Improve-lmc-no-virt-error-handling.patch
diff options
context:
space:
mode:
Diffstat (limited to 'backport-Improve-lmc-no-virt-error-handling.patch')
-rw-r--r--backport-Improve-lmc-no-virt-error-handling.patch163
1 files changed, 163 insertions, 0 deletions
diff --git a/backport-Improve-lmc-no-virt-error-handling.patch b/backport-Improve-lmc-no-virt-error-handling.patch
new file mode 100644
index 0000000..38ce3e0
--- /dev/null
+++ b/backport-Improve-lmc-no-virt-error-handling.patch
@@ -0,0 +1,163 @@
+From 6400515880e59ab7d0d68a848e2f57052faa0d30 Mon Sep 17 00:00:00 2001
+From: "Brian C. Lane" <bcl@redhat.com>
+Date: Tue, 8 Dec 2020 15:57:51 -0800
+Subject: [PATCH] Improve lmc no-virt error handling
+
+When monitoring log output in livemedia-creator --no-virt it could get
+stuck if the output from anaconda stops for some reason.
+
+This changes execReadlines so that it will only read output when it is
+available, will monitor the process state, and continue to call the
+callback function.
+
+It also adds a final timeout on proc.communicate() so that if Anaconda
+becomes stuck and won't exit livemedia-creator will eventually exit.
+
+When the no-virt callback terminates anaconda on an error it now sends a
+TERM signal to all of the unshare process' children because just sending
+it to unshare doesn't cause anaconda to exit.
+---
+ lorax.spec | 1 +
+ src/pylorax/executils.py | 56 ++++++++++++++++++++++++++++++++++--------------
+ src/pylorax/installer.py | 9 ++++++--
+ test-packages | 1 +
+ 4 files changed, 49 insertions(+), 18 deletions(-)
+
+diff --git a/lorax.spec b/lorax.spec
+index 40506b0..52cda64 100644
+--- a/lorax.spec
++++ b/lorax.spec
+@@ -118,6 +118,7 @@ Requires: anaconda-core
+ Requires: anaconda-tui
+ Requires: anaconda-install-env-deps
+ Requires: system-logos
++Requires: python3-psutil
+
+ %description lmc-novirt
+ Additional dependencies required by livemedia-creator when using it with --no-virt
+diff --git a/src/pylorax/executils.py b/src/pylorax/executils.py
+index da5df60..ffb26b6 100644
+--- a/src/pylorax/executils.py
++++ b/src/pylorax/executils.py
+@@ -19,9 +19,11 @@
+ #
+
+ import os
++import select
+ import subprocess
+ from subprocess import TimeoutExpired
+ import signal
++import time
+
+ import logging
+ log = logging.getLogger("pylorax")
+@@ -288,6 +290,7 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None, filter_st
+ self._proc = proc
+ self._argv = argv
+ self._callback = callback
++ self._data = ""
+
+ def __iter__(self):
+ return self
+@@ -302,22 +305,43 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None, filter_st
+ pass
+
+ def __next__(self):
+- # Read the next line, blocking if a line is not yet available
+- line = self._proc.stdout.readline().decode("utf-8")
+- if line == '' or not self._callback(self._proc):
+- # Output finished, wait for the process to end
+- self._proc.communicate()
+-
+- # Check for successful exit
+- if self._proc.returncode < 0:
+- raise OSError("process '%s' was killed by signal %s" %
+- (self._argv, -self._proc.returncode))
+- elif self._proc.returncode > 0:
+- raise OSError("process '%s' exited with status %s" %
+- (self._argv, self._proc.returncode))
+- raise StopIteration
+-
+- return line.strip()
++ # Return lines from stdout while also calling _callback
++ while True:
++ # Check for input without blocking
++ if select.select([self._proc.stdout], [], [], 0)[0]:
++ size = len(self._proc.stdout.peek(1))
++ if size > 0:
++ self._data += self._proc.stdout.read(size).decode("utf-8")
++
++ if self._data.find("\n") >= 0:
++ line = self._data.split("\n", 1)
++ self._data = line[1]
++ return line[0]
++
++ if self._proc.poll() is not None or not self._callback(self._proc):
++ # Output finished, wait 60s for the process to end
++ try:
++ self._proc.communicate(timeout=60)
++ except subprocess.TimeoutExpired:
++ # Did not exit in 60s, kill it and wait 30s more
++ self._proc.kill()
++ try:
++ self._proc.communicate(timeout=30)
++ except subprocess.TimeoutExpired:
++ pass
++
++ if self._proc.returncode is None:
++ raise OSError("process '%s' failed to be killed" % self._argv)
++ elif self._proc.returncode < 0:
++ raise OSError("process '%s' was killed by signal %s" %
++ (self._argv, -self._proc.returncode))
++ elif self._proc.returncode > 0:
++ raise OSError("process '%s' exited with status %s" %
++ (self._argv, self._proc.returncode))
++ raise StopIteration
++
++ # Don't loop too fast with no input to read
++ time.sleep(0.5)
+
+ argv = [command] + argv
+
+diff --git a/src/pylorax/installer.py b/src/pylorax/installer.py
+index 9d0a852..1528474 100644
+--- a/src/pylorax/installer.py
++++ b/src/pylorax/installer.py
+@@ -291,7 +291,12 @@ def novirt_cancel_check(cancel_funcs, proc):
+ """
+ for f in cancel_funcs:
+ if f():
+- proc.terminate()
++ # Anaconda runs from unshare, anaconda doesn't exit correctly so try to
++ # send TERM to all of them directly
++ import psutil
++ for p in psutil.Process(proc.pid).children(recursive=True):
++ p.terminate()
++ psutil.Process(proc.pid).terminate()
+ return True
+ return False
+
+@@ -401,7 +406,7 @@ def novirt_install(opts, disk_img, disk_size, cancel_func=None, tar_img=None):
+ # Preload libgomp.so.1 to workaround rhbz#1722181
+ log.info("Running anaconda.")
+ try:
+- unshare_args = [ "--pid", "--kill-child", "--mount", "--propagation", "unchanged", "anaconda" ] + args
++ unshare_args = ["--pid", "--kill-child", "--mount", "--propagation", "unchanged", "anaconda"] + args
+ for line in execReadlines("unshare", unshare_args, reset_lang=False,
+ env_add={"ANACONDA_PRODUCTNAME": opts.project,
+ "ANACONDA_PRODUCTVERSION": opts.releasever,
+diff --git a/test-packages b/test-packages
+index bc5bf20..77583c7 100644
+--- a/test-packages
++++ b/test-packages
+@@ -12,6 +12,7 @@ python3-librepo
+ python3-magic
+ python3-mako
+ python3-pocketlint
++python3-psutil
+ python3-pycdlib
+ python3-pylint
+ python3-pyparted
+--
+1.8.3.1
+