Add cancel_func to virt and novirt_install functions

In addition to monitoring the logs for errors, call a function (or
functions) that tell it to cancel the anaconda process and cleanup.

Also check for a cancel after creating the squashfs image for live-iso
since that's a long running process.

This required adding a new argument to a number of existing functions,
passing it down to QEMUInstall and novirt_install where the function is
called.

Resolves: rhbz#1656691
This commit is contained in:
Brian C. Lane 2018-12-11 11:37:21 -08:00
parent df6d7654bd
commit 850ad9845a
3 changed files with 51 additions and 25 deletions

View File

@ -245,7 +245,7 @@ def make_compose(cfg, results_dir):
else: else:
open(joinpaths(results_dir, install_cfg.image_name), "w").write("TEST IMAGE") open(joinpaths(results_dir, install_cfg.image_name), "w").write("TEST IMAGE")
else: else:
run_creator(install_cfg, callback_func=cancel_build) run_creator(install_cfg, cancel_func=cancel_build)
# Extract the results of the compose into results_dir and cleanup the compose directory # Extract the results of the compose into results_dir and cleanup the compose directory
move_compose_results(install_cfg, results_dir) move_compose_results(install_cfg, results_dir)

View File

@ -456,13 +456,15 @@ def calculate_disk_size(opts, ks):
log.info("Using disk size of %sMiB", disk_size) log.info("Using disk size of %sMiB", disk_size)
return disk_size return disk_size
def make_image(opts, ks): def make_image(opts, ks, cancel_func=None):
""" """
Install to a disk image Install to a disk image
:param opts: options passed to livemedia-creator :param opts: options passed to livemedia-creator
:type opts: argparse options :type opts: argparse options
:param str ks: Path to the kickstart to use for the installation :param str ks: Path to the kickstart to use for the installation
:param cancel_func: Function that returns True to cancel build
:type cancel_func: function
:returns: Path of the image created :returns: Path of the image created
:rtype: str :rtype: str
@ -476,12 +478,12 @@ def make_image(opts, ks):
disk_size = calculate_disk_size(opts, ks) disk_size = calculate_disk_size(opts, ks)
try: try:
if opts.no_virt: if opts.no_virt:
novirt_install(opts, disk_img, disk_size) novirt_install(opts, disk_img, disk_size, cancel_func=cancel_func)
else: else:
install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log"
log.info("install_log = %s", install_log) log.info("install_log = %s", install_log)
virt_install(opts, install_log, disk_img, disk_size) virt_install(opts, install_log, disk_img, disk_size, cancel_func=cancel_func)
except InstallError as e: except InstallError as e:
log.error("Install failed: %s", e) log.error("Install failed: %s", e)
if not opts.keep_image and os.path.exists(disk_img): if not opts.keep_image and os.path.exists(disk_img):
@ -576,11 +578,13 @@ def make_live_images(opts, work_dir, disk_img):
return work_dir return work_dir
def run_creator(opts, callback_func=None): def run_creator(opts, cancel_func=None):
"""Run the image creator process """Run the image creator process
:param opts: Commandline options to control the process :param opts: Commandline options to control the process
:type opts: Either a DataHolder or ArgumentParser :type opts: Either a DataHolder or ArgumentParser
:param cancel_func: Function that returns True to cancel build
:type cancel_func: function
:returns: The result directory and the disk image path. :returns: The result directory and the disk image path.
:rtype: Tuple of str :rtype: Tuple of str
@ -639,7 +643,7 @@ def run_creator(opts, callback_func=None):
# Make the image. Output of this is either a partitioned disk image or a fsimage # Make the image. Output of this is either a partitioned disk image or a fsimage
try: try:
disk_img = make_image(opts, ks) disk_img = make_image(opts, ks, cancel_func=cancel_func)
except InstallError as e: except InstallError as e:
log.error("ERROR: Image creation failed: %s", e) log.error("ERROR: Image creation failed: %s", e)
raise RuntimeError("Image creation failed: %s" % e) raise RuntimeError("Image creation failed: %s" % e)
@ -659,6 +663,9 @@ def run_creator(opts, callback_func=None):
log.error("squashfs.img creation failed") log.error("squashfs.img creation failed")
raise RuntimeError("squashfs.img creation failed") raise RuntimeError("squashfs.img creation failed")
if cancel_func():
raise RuntimeError("ISO creation canceled")
with Mount(disk_img, opts="loop") as mount_dir: with Mount(disk_img, opts="loop") as mount_dir:
result_dir = make_livecd(opts, mount_dir, work_dir) result_dir = make_livecd(opts, mount_dir, work_dir)
else: else:

View File

@ -146,7 +146,7 @@ class QEMUInstall(object):
def __init__(self, opts, iso, ks_paths, disk_img, img_size=2048, def __init__(self, opts, iso, ks_paths, disk_img, img_size=2048,
kernel_args=None, memory=1024, vcpus=None, vnc=None, arch=None, kernel_args=None, memory=1024, vcpus=None, vnc=None, arch=None,
log_check=None, virtio_host="127.0.0.1", virtio_port=6080, cancel_func=None, virtio_host="127.0.0.1", virtio_port=6080,
image_type=None, boot_uefi=False, ovmf_path=None): image_type=None, boot_uefi=False, ovmf_path=None):
""" """
Start the installation Start the installation
@ -162,8 +162,8 @@ class QEMUInstall(object):
:param int vcpus: Number of virtual cpus :param int vcpus: Number of virtual cpus
:param str vnc: Arguments to pass to qemu -display :param str vnc: Arguments to pass to qemu -display
:param str arch: Optional architecture to use in the virt :param str arch: Optional architecture to use in the virt
:param log_check: Method that returns True if the installation fails :param cancel_func: Function that returns True if the installation fails
:type log_check: method :type cancel_func: function
:param str virtio_host: Hostname to connect virtio log to :param str virtio_host: Hostname to connect virtio log to
:param int virtio_port: Port to connect virtio log to :param int virtio_port: Port to connect virtio log to
:param str image_type: Type of qemu-img disk to create, or None. :param str image_type: Type of qemu-img disk to create, or None.
@ -249,7 +249,7 @@ class QEMUInstall(object):
log.debug(qemu_cmd) log.debug(qemu_cmd)
try: try:
execWithRedirect(qemu_cmd[0], qemu_cmd[1:], reset_lang=False, raise_err=True, execWithRedirect(qemu_cmd[0], qemu_cmd[1:], reset_lang=False, raise_err=True,
callback=lambda p: not log_check()) callback=lambda p: not cancel_func())
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
log.error("Running qemu failed:") log.error("Running qemu failed:")
log.error("cmd: %s", " ".join(e.cmd)) log.error("cmd: %s", " ".join(e.cmd))
@ -263,27 +263,30 @@ class QEMUInstall(object):
if boot_uefi and ovmf_path: if boot_uefi and ovmf_path:
os.unlink(ovmf_vars) os.unlink(ovmf_vars)
if log_check(): if cancel_func():
log.error("Installation error detected. See logfile for details.") log.error("Installation error detected. See logfile for details.")
raise InstallError("QEMUInstall failed") raise InstallError("QEMUInstall failed")
else: else:
log.info("Installation finished without errors.") log.info("Installation finished without errors.")
def novirt_log_check(log_check, proc): def novirt_cancel_check(cancel_funcs, proc):
""" """
Check to see if there has been an error in the logs 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 cancel_funcs: list of functions to call, True from any one cancels the build
:type cancel_funcs: list
:param proc: Popen object for the anaconda process :param proc: Popen object for the anaconda process
:type proc: subprocess.Popen
:returns: True if the process has been terminated :returns: True if the process has been terminated
The log_check method should return a True if an error has been detected. The cancel_funcs functions should return a True if an error has been detected.
When an error is detected the process is terminated and this returns True When an error is detected the process is terminated and this returns True
""" """
if log_check(): for f in cancel_funcs:
proc.terminate() if f():
return True proc.terminate()
return True
return False return False
@ -314,7 +317,7 @@ def anaconda_cleanup(dirinstall_path):
return rc return rc
def novirt_install(opts, disk_img, disk_size): def novirt_install(opts, disk_img, disk_size, cancel_func=None):
""" """
Use Anaconda to install to a disk image Use Anaconda to install to a disk image
@ -322,6 +325,8 @@ def novirt_install(opts, disk_img, disk_size):
:type opts: argparse options :type opts: argparse options
:param str disk_img: The full path to the disk image to be created :param str disk_img: The full path to the disk image to be created
:param int disk_size: The size of the disk_img in MiB :param int disk_size: The size of the disk_img in MiB
:param cancel_func: Function that returns True to cancel build
:type cancel_func: function
This method runs anaconda to create the image and then based on the opts This method runs anaconda to create the image and then based on the opts
passed creates a qemu disk image or tarfile. passed creates a qemu disk image or tarfile.
@ -371,6 +376,9 @@ def novirt_install(opts, disk_img, disk_size):
log_monitor = LogMonitor(timeout=opts.timeout) log_monitor = LogMonitor(timeout=opts.timeout)
args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)] args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)]
cancel_funcs = [log_monitor.server.log_check]
if cancel_func is not None:
cancel_funcs.append(cancel_func)
# Make sure anaconda has the right product and release # Make sure anaconda has the right product and release
log.info("Running anaconda.") log.info("Running anaconda.")
@ -378,7 +386,7 @@ def novirt_install(opts, disk_img, disk_size):
for line in execReadlines("anaconda", args, reset_lang=False, for line in execReadlines("anaconda", args, reset_lang=False,
env_add={"ANACONDA_PRODUCTNAME": opts.project, env_add={"ANACONDA_PRODUCTNAME": opts.project,
"ANACONDA_PRODUCTVERSION": opts.releasever}, "ANACONDA_PRODUCTVERSION": opts.releasever},
callback=lambda p: not novirt_log_check(log_monitor.server.log_check, p)): callback=lambda p: not novirt_cancel_check(cancel_funcs, p)):
log.info(line) log.info(line)
# Make sure the new filesystem is correctly labeled # Make sure the new filesystem is correctly labeled
@ -424,10 +432,14 @@ def novirt_install(opts, disk_img, disk_size):
if not opts.make_iso and not opts.make_fsimage and not opts.make_pxe_live: if not opts.make_iso and not opts.make_fsimage and not opts.make_pxe_live:
dm_name = os.path.splitext(os.path.basename(disk_img))[0] dm_name = os.path.splitext(os.path.basename(disk_img))[0]
dm_path = "/dev/mapper/"+dm_name
if os.path.exists(dm_path): # Remove device-mapper for partitions and disk
dm_detach(dm_path) log.debug("Removing device-mapper setup on %s", dm_name)
loop_detach(get_loop_name(disk_img)) for d in sorted(glob.glob("/dev/mapper/"+dm_name+"*"), reverse=True):
dm_detach(d)
log.debug("Removing loop device for %s", disk_img)
loop_detach("/dev/"+get_loop_name(disk_img))
# qemu disk image is used by bare qcow2 images and by Vagrant # qemu disk image is used by bare qcow2 images and by Vagrant
if opts.image_type: if opts.image_type:
@ -493,7 +505,7 @@ def novirt_install(opts, disk_img, disk_size):
execWithRedirect("fallocate", ["--dig-holes", disk_img], raise_err=True) execWithRedirect("fallocate", ["--dig-holes", disk_img], raise_err=True)
def virt_install(opts, install_log, disk_img, disk_size): def virt_install(opts, install_log, disk_img, disk_size, cancel_func=None):
""" """
Use qemu to install to a disk image Use qemu to install to a disk image
@ -502,6 +514,8 @@ def virt_install(opts, install_log, disk_img, disk_size):
:param str install_log: The path to write the log from qemu :param str install_log: The path to write the log from qemu
:param str disk_img: The full path to the disk image to be created :param str disk_img: The full path to the disk image to be created
:param int disk_size: The size of the disk_img in MiB :param int disk_size: The size of the disk_img in MiB
:param cancel_func: Function that returns True to cancel build
:type cancel_func: function
This uses qemu with a boot.iso and a kickstart to create a disk This uses qemu with a boot.iso and a kickstart to create a disk
image and then optionally, based on the opts passed, creates tarfile. image and then optionally, based on the opts passed, creates tarfile.
@ -512,6 +526,9 @@ def virt_install(opts, install_log, disk_img, disk_size):
raise InstallError("ISO is missing stage2, cannot continue") raise InstallError("ISO is missing stage2, cannot continue")
log_monitor = LogMonitor(install_log, timeout=opts.timeout) log_monitor = LogMonitor(install_log, timeout=opts.timeout)
cancel_funcs = [log_monitor.server.log_check]
if cancel_func is not None:
cancel_funcs.append(cancel_func)
kernel_args = "" kernel_args = ""
if opts.kernel_args: if opts.kernel_args:
@ -536,7 +553,7 @@ def virt_install(opts, install_log, disk_img, disk_size):
try: try:
QEMUInstall(opts, iso_mount, opts.ks, diskimg_path, disk_size, QEMUInstall(opts, iso_mount, opts.ks, diskimg_path, disk_size,
kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch, kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch,
log_check = log_monitor.server.log_check, cancel_func = lambda : any(f() for f in cancel_funcs),
virtio_host = log_monitor.host, virtio_host = log_monitor.host,
virtio_port = log_monitor.port, virtio_port = log_monitor.port,
image_type=opts.image_type, boot_uefi=opts.virt_uefi, image_type=opts.image_type, boot_uefi=opts.virt_uefi,
@ -555,6 +572,8 @@ def virt_install(opts, install_log, disk_img, disk_size):
else: else:
msg = "virt_install failed on line: %s" % log_monitor.server.error_line msg = "virt_install failed on line: %s" % log_monitor.server.error_line
raise InstallError(msg) raise InstallError(msg)
elif cancel_func():
raise InstallError("virt_install canceled by cancel_func")
if opts.make_fsimage: if opts.make_fsimage:
mkfsimage_from_disk(diskimg_path, disk_img, disk_size, label=opts.fs_label) mkfsimage_from_disk(diskimg_path, disk_img, disk_size, label=opts.fs_label)