[PATCH] Add log monitoring to lmc --novirt installation

Brian C. Lane bcl at redhat.com
Fri Jan 9 22:59:55 UTC 2015


Previously if there was an error during a novirt installation that
didn't exit the process there was no way to detect it. This uses the new
--remotelog option for anaconda to monitor the logs for errors using the
same criteria as it does when monitoring a virt install. If there is an
error the anaconda process will be terminated and the logs will be
gathered up into ./anaconda/
---
 src/pylorax/executils.py   | 10 +++++++++-
 src/sbin/livemedia-creator | 40 ++++++++++++++++++++++++++++++++++------
 2 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/src/pylorax/executils.py b/src/pylorax/executils.py
index 0b261c3..6fbb271 100644
--- a/src/pylorax/executils.py
+++ b/src/pylorax/executils.py
@@ -23,6 +23,7 @@
 import os, sys
 import subprocess
 import threading
+from time import sleep
 
 import logging
 log = logging.getLogger("pylorax")
@@ -66,7 +67,7 @@ class tee(threading.Thread):
 
 def execWithRedirect(command, argv, stdin = None, stdout = None,
                      stderr = None, root = None, preexec_fn=None, cwd=None,
-                     raise_err=False):
+                     raise_err=False, callback=None):
     """ Run an external program and redirect the output to a file.
         @param command The command to run.
         @param argv A list of arguments.
@@ -77,7 +78,11 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
         @param preexec_fn function to pass to Popen
         @param cwd working directory to pass to Popen
         @param raise_err raise CalledProcessError when the returncode is not 0
+        @param callback method to call while waiting for process to exit.
         @return The return code of command.
+
+        The callback is passed the Popen object. It should return False if
+        the polling loop should be exited.
     """
     def chroot ():
         os.chroot(root)
@@ -143,6 +148,9 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
                                 preexec_fn=preexec_fn, cwd=cwd,
                                 env=env)
 
+        if callback:
+            while callback(proc) and proc.poll() is None:
+                sleep(1)
         proc.wait()
         ret = proc.returncode
 
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator
index 1472f21..b21cdbe 100755
--- a/src/sbin/livemedia-creator
+++ b/src/sbin/livemedia-creator
@@ -88,7 +88,7 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
         if self.server.log_path:
             self.fp = open(self.server.log_path, "w") # pylint: disable=attribute-defined-outside-init
         else:
-            print "no log_path specified"
+            self.fp = None
         self.request.settimeout(10)
 
     def handle(self):
@@ -107,8 +107,9 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
 
             try:
                 data = self.request.recv(4096)
-                self.fp.write(data)
-                self.fp.flush()
+                if self.fp:
+                    self.fp.write(data)
+                    self.fp.flush()
 
                 # check the data for errors and set error flag
                 # need to assemble it into lines so we can test for the error
@@ -130,7 +131,8 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
 
     def finish(self):
         self.request.close()
-        self.fp.close()
+        if self.fp:
+            self.fp.close()
 
     def iserror(self, line):
         """
@@ -193,7 +195,7 @@ class LogMonitor(object):
     This needs to be running before the virt-install runs, it expects
     there to be a listener on the port used for the virtio log port.
     """
-    def __init__(self, log_path, host="localhost", port=0):
+    def __init__(self, log_path=None, host="localhost", port=0):
         """
         Start a thread to monitor the logs.
 
@@ -203,6 +205,9 @@ class LogMonitor(object):
 
         If 0 is passed for the port the dynamically assigned port will be
         available as self.port
+
+        If log_path isn't set then it only monitors the logs, instead of
+        also writing them to disk.
         """
         self.server = LogServer(log_path, (host, port), LogRequestHandler)
         self.host, self.port = self.server.server_address
@@ -590,6 +595,23 @@ def make_livecd(opts, mount_dir, work_dir):
     return work_dir
 
 
+def novirt_log_check(log_check, proc):
+    """
+    Check to see if there has been an error in the logs
+
+    :param log_check: method to call to check for an error in the logs
+    :param proc: Popen object for the anaconda process
+    :returns: True if the process has been terminated
+
+    The log_check method should return a True if an error has been detected.
+    When an error is detected the process is terminated and this returns True
+    """
+    if log_check():
+        proc.terminate()
+        return True
+    return False
+
+
 def novirt_install(opts, disk_img, disk_size, repo_url):
     """
     Use Anaconda to install to a disk image
@@ -648,12 +670,16 @@ def novirt_install(opts, disk_img, disk_size, repo_url):
         # Create the sparse image
         mksparse(disk_img, disk_size * 1024**2)
 
+    log_monitor = LogMonitor()
+    args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)]
+
     # Make sure anaconda has the right product and release
     os.environ["ANACONDA_PRODUCTNAME"] = opts.project
     os.environ["ANACONDA_PRODUCTVERSION"] = opts.releasever
     log.info("Running anaconda.")
     try:
-        execWithRedirect("anaconda", args, raise_err=True)
+        execWithRedirect("anaconda", args, raise_err=True,
+                         callback=lambda p: not novirt_log_check(log_monitor.server.log_check, p))
 
         # Make sure the new filesystem is correctly labeled
         args = ["-e", "/proc", "-e", "/sys", "-e", "/dev",
@@ -668,6 +694,8 @@ def novirt_install(opts, disk_img, disk_size, repo_url):
         log.error("Running anaconda failed: %s", e)
         raise InstallError("novirt_install failed")
     finally:
+        log_monitor.shutdown()
+
         # Move the anaconda logs over to a log directory
         log_dir = os.path.abspath(os.path.dirname(opts.logfile))
         log_anaconda = joinpaths(log_dir, "anaconda")
-- 
1.9.3



More information about the anaconda-patches mailing list