From 850ad9845abaa824bfd78af043b10bd0e47788c9 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 11 Dec 2018 11:37:21 -0800 Subject: [PATCH] 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 --- src/pylorax/api/queue.py | 2 +- src/pylorax/creator.py | 17 ++++++++---- src/pylorax/installer.py | 57 ++++++++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/pylorax/api/queue.py b/src/pylorax/api/queue.py index 8666ccdf..e69778d1 100644 --- a/src/pylorax/api/queue.py +++ b/src/pylorax/api/queue.py @@ -245,7 +245,7 @@ def make_compose(cfg, results_dir): else: open(joinpaths(results_dir, install_cfg.image_name), "w").write("TEST IMAGE") 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 move_compose_results(install_cfg, results_dir) diff --git a/src/pylorax/creator.py b/src/pylorax/creator.py index e22d5321..a070170d 100644 --- a/src/pylorax/creator.py +++ b/src/pylorax/creator.py @@ -456,13 +456,15 @@ def calculate_disk_size(opts, ks): log.info("Using disk size of %sMiB", disk_size) return disk_size -def make_image(opts, ks): +def make_image(opts, ks, cancel_func=None): """ Install to a disk image :param opts: options passed to livemedia-creator :type opts: argparse options :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 :rtype: str @@ -476,12 +478,12 @@ def make_image(opts, ks): disk_size = calculate_disk_size(opts, ks) try: if opts.no_virt: - novirt_install(opts, disk_img, disk_size) + novirt_install(opts, disk_img, disk_size, cancel_func=cancel_func) else: install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-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: log.error("Install failed: %s", e) 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 -def run_creator(opts, callback_func=None): +def run_creator(opts, cancel_func=None): """Run the image creator process :param opts: Commandline options to control the process :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. :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 try: - disk_img = make_image(opts, ks) + disk_img = make_image(opts, ks, cancel_func=cancel_func) except InstallError as e: log.error("ERROR: 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") raise RuntimeError("squashfs.img creation failed") + if cancel_func(): + raise RuntimeError("ISO creation canceled") + with Mount(disk_img, opts="loop") as mount_dir: result_dir = make_livecd(opts, mount_dir, work_dir) else: diff --git a/src/pylorax/installer.py b/src/pylorax/installer.py index 0d86e75a..ec9e9d69 100644 --- a/src/pylorax/installer.py +++ b/src/pylorax/installer.py @@ -146,7 +146,7 @@ class QEMUInstall(object): def __init__(self, opts, iso, ks_paths, disk_img, img_size=2048, 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): """ Start the installation @@ -162,8 +162,8 @@ class QEMUInstall(object): :param int vcpus: Number of virtual cpus :param str vnc: Arguments to pass to qemu -display :param str arch: Optional architecture to use in the virt - :param log_check: Method that returns True if the installation fails - :type log_check: method + :param cancel_func: Function that returns True if the installation fails + :type cancel_func: function :param str virtio_host: Hostname 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. @@ -249,7 +249,7 @@ class QEMUInstall(object): log.debug(qemu_cmd) try: 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: log.error("Running qemu failed:") log.error("cmd: %s", " ".join(e.cmd)) @@ -263,27 +263,30 @@ class QEMUInstall(object): if boot_uefi and ovmf_path: os.unlink(ovmf_vars) - if log_check(): + if cancel_func(): log.error("Installation error detected. See logfile for details.") raise InstallError("QEMUInstall failed") else: 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 - :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 + :type proc: subprocess.Popen :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 """ - if log_check(): - proc.terminate() - return True + for f in cancel_funcs: + if f(): + proc.terminate() + return True return False @@ -314,7 +317,7 @@ def anaconda_cleanup(dirinstall_path): 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 @@ -322,6 +325,8 @@ def novirt_install(opts, disk_img, disk_size): :type opts: argparse options :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 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 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) 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 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, env_add={"ANACONDA_PRODUCTNAME": opts.project, "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) # 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: dm_name = os.path.splitext(os.path.basename(disk_img))[0] - dm_path = "/dev/mapper/"+dm_name - if os.path.exists(dm_path): - dm_detach(dm_path) - loop_detach(get_loop_name(disk_img)) + + # Remove device-mapper for partitions and disk + log.debug("Removing device-mapper setup on %s", dm_name) + 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 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) -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 @@ -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 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 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 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") 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 = "" if opts.kernel_args: @@ -536,7 +553,7 @@ def virt_install(opts, install_log, disk_img, disk_size): try: QEMUInstall(opts, iso_mount, opts.ks, diskimg_path, disk_size, 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_port = log_monitor.port, 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: msg = "virt_install failed on line: %s" % log_monitor.server.error_line raise InstallError(msg) + elif cancel_func(): + raise InstallError("virt_install canceled by cancel_func") if opts.make_fsimage: mkfsimage_from_disk(diskimg_path, disk_img, disk_size, label=opts.fs_label)