From d96ed99621c12026bac8be21e0370ac63f200c79 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 9 Jan 2015 13:13:55 -0800 Subject: [PATCH] Add log monitoring to lmc --no-virt installation 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 0b261c36..6fbb271b 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 1472f21a..b21cdbe3 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")