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/
This commit is contained in:
Brian C. Lane 2015-01-09 13:13:55 -08:00
parent bd501cccef
commit d96ed99621
2 changed files with 43 additions and 7 deletions

View File

@ -23,6 +23,7 @@
import os, sys import os, sys
import subprocess import subprocess
import threading import threading
from time import sleep
import logging import logging
log = logging.getLogger("pylorax") log = logging.getLogger("pylorax")
@ -66,7 +67,7 @@ class tee(threading.Thread):
def execWithRedirect(command, argv, stdin = None, stdout = None, def execWithRedirect(command, argv, stdin = None, stdout = None,
stderr = None, root = None, preexec_fn=None, cwd=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. """ Run an external program and redirect the output to a file.
@param command The command to run. @param command The command to run.
@param argv A list of arguments. @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 preexec_fn function to pass to Popen
@param cwd working directory to pass to Popen @param cwd working directory to pass to Popen
@param raise_err raise CalledProcessError when the returncode is not 0 @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. @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 (): def chroot ():
os.chroot(root) os.chroot(root)
@ -143,6 +148,9 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
preexec_fn=preexec_fn, cwd=cwd, preexec_fn=preexec_fn, cwd=cwd,
env=env) env=env)
if callback:
while callback(proc) and proc.poll() is None:
sleep(1)
proc.wait() proc.wait()
ret = proc.returncode ret = proc.returncode

View File

@ -88,7 +88,7 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
if self.server.log_path: if self.server.log_path:
self.fp = open(self.server.log_path, "w") # pylint: disable=attribute-defined-outside-init self.fp = open(self.server.log_path, "w") # pylint: disable=attribute-defined-outside-init
else: else:
print "no log_path specified" self.fp = None
self.request.settimeout(10) self.request.settimeout(10)
def handle(self): def handle(self):
@ -107,6 +107,7 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
try: try:
data = self.request.recv(4096) data = self.request.recv(4096)
if self.fp:
self.fp.write(data) self.fp.write(data)
self.fp.flush() self.fp.flush()
@ -130,6 +131,7 @@ class LogRequestHandler(SocketServer.BaseRequestHandler):
def finish(self): def finish(self):
self.request.close() self.request.close()
if self.fp:
self.fp.close() self.fp.close()
def iserror(self, line): def iserror(self, line):
@ -193,7 +195,7 @@ class LogMonitor(object):
This needs to be running before the virt-install runs, it expects 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. 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. 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 If 0 is passed for the port the dynamically assigned port will be
available as self.port 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.server = LogServer(log_path, (host, port), LogRequestHandler)
self.host, self.port = self.server.server_address self.host, self.port = self.server.server_address
@ -590,6 +595,23 @@ def make_livecd(opts, mount_dir, work_dir):
return 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): def novirt_install(opts, disk_img, disk_size, repo_url):
""" """
Use Anaconda to install to a disk image 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 # Create the sparse image
mksparse(disk_img, disk_size * 1024**2) 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 # Make sure anaconda has the right product and release
os.environ["ANACONDA_PRODUCTNAME"] = opts.project os.environ["ANACONDA_PRODUCTNAME"] = opts.project
os.environ["ANACONDA_PRODUCTVERSION"] = opts.releasever os.environ["ANACONDA_PRODUCTVERSION"] = opts.releasever
log.info("Running anaconda.") log.info("Running anaconda.")
try: 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 # Make sure the new filesystem is correctly labeled
args = ["-e", "/proc", "-e", "/sys", "-e", "/dev", 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) log.error("Running anaconda failed: %s", e)
raise InstallError("novirt_install failed") raise InstallError("novirt_install failed")
finally: finally:
log_monitor.shutdown()
# Move the anaconda logs over to a log directory # Move the anaconda logs over to a log directory
log_dir = os.path.abspath(os.path.dirname(opts.logfile)) log_dir = os.path.abspath(os.path.dirname(opts.logfile))
log_anaconda = joinpaths(log_dir, "anaconda") log_anaconda = joinpaths(log_dir, "anaconda")