[PATCH lmc atomic] (v2) Add --make-pxe-live and --make-ostree-live (for Atomic) targets.

Radek Vykydal rvykydal at redhat.com
Wed Jan 7 11:27:34 UTC 2015


On 01/06/2015 04:21 PM, Radek Vykydal wrote:
> v2 is addressing Brian's review remarks. Also example kickstart is modified
> to use local repo from Atomic installer iso.

It can be helpful to see the diff to the previous version for review:
https://github.com/rvykydal/lorax/commit/3c0fb11627c003bc9c8cfd6f2bd757fe7f8bf3ce

> --make-pxe-live target generate live squashfs and initrd for pxe boot.
> Also generates pxe config template.
>
> --make-ostree-live is used for installations of Atomic Host. Additionally to
> --make-pxe-live it ensures using deployment root instead of physical root of
> installed disk image where needed. Atomic installation needs to be virt
> installation with /boot on separate partition (the only way supported by
> Anaconda currently). Content of boot partition is added to live root fs so that
> ostree can find deployment by boot configuration.
> ---
>   README.livemedia-creator       |  16 +++
>   docs/livemedia-creator.1       |  10 +-
>   docs/rhel-atomic-pxe-live.ks   |  28 +++++
>   share/pxe-live/pxe-config.tmpl |   3 +
>   src/pylorax/imgutils.py        |  24 +++++
>   src/pylorax/treebuilder.py     |  16 +--
>   src/sbin/livemedia-creator     | 227 ++++++++++++++++++++++++++++++++++++++++-
>   7 files changed, 307 insertions(+), 17 deletions(-)
>   create mode 100644 docs/rhel-atomic-pxe-live.ks
>   create mode 100644 share/pxe-live/pxe-config.tmpl
>
> diff --git a/README.livemedia-creator b/README.livemedia-creator
> index 419dce3..05357ac 100644
> --- a/README.livemedia-creator
> +++ b/README.livemedia-creator
> @@ -256,6 +256,22 @@ eg.
>   livemedia-creator --make-tar --iso=/path/to/boot.iso --ks=./docs/fedora-minimal.ks \
>   --image-name=fedora-root.tar.xz
>   
> +LIVE IMAGE FOR PXE BOOT
> +-----------------------
> +
> +The --make-pxe-live command will produce squashfs image containing live root
> +filesystem that can be used for pxe boot. Directory with results will contain
> +the live image, kernel image, initrd image and template of pxe configuration
> +for the images.
> +
> +ATOMIC LIVE IMAGE FOR PXE BOOT
> +------------------------------
> +
> +The --make-ostree-live command will produce the same result as --make-pxe-live
> +for installations of Atomic Host.  Example kickstart for such an installation
> +using Atomic installer iso with local repo included in the image can be found
> +in docs/rhel-atomic-pxe-live.ks.
> +
>   
>   DEBUGGING PROBLEMS
>   ------------------
> diff --git a/docs/livemedia-creator.1 b/docs/livemedia-creator.1
> index 1b0ad20..32a9fe5 100644
> --- a/docs/livemedia-creator.1
> +++ b/docs/livemedia-creator.1
> @@ -4,7 +4,7 @@ livemedia-creator \- Create live install media
>   
>   .SH SYNOPSIS
>   livemedia-creator [-h]
> -    (--make-iso | --make-disk | --make-fsimage | --make-appliance | --make-ami | --make-tar)
> +    (--make-iso | --make-disk | --make-fsimage | --make-appliance | --make-ami | --make-tar | --make-pxe-live | --make-ostree-live)
>       [--iso ISO] [--disk-image DISK_IMAGE]
>       [--fs-image FS_IMAGE] [--ks KS]
>       [--image-name IMAGE_NAME] [--image-only]
> @@ -71,6 +71,14 @@ Build an ami image
>   Build a tar of the root filesystem. Defaults to root.tar.xz
>   
>   .TP
> +\fB\-\-make\-pxe\-live\fR
> +Build a live pxe boot squashfs image
> +
> +.TP
> +\fB\-\-make\-ostree\-live\fR
> +Build a live pxe boot squashfs image of Atomic Host
> +
> +.TP
>   \fB\-\-iso ISO\fR
>   Anaconda installation .iso path to use for virt-install
>   
> diff --git a/docs/rhel-atomic-pxe-live.ks b/docs/rhel-atomic-pxe-live.ks
> new file mode 100644
> index 0000000..1d8f63f
> --- /dev/null
> +++ b/docs/rhel-atomic-pxe-live.ks
> @@ -0,0 +1,28 @@
> +# Settings for unattended installation:
> +lang en_US.UTF-8
> +keyboard us
> +timezone America/New_York
> +zerombr
> +clearpart --all --initlabel
> +rootpw --plaintext atomic
> +network --bootproto=dhcp --device=link --activate
> +
> +# We are only able to install atomic with separate /boot partition currently
> +part / --fstype="ext4" --size=6000
> +part /boot --size=500 --fstype="ext4"
> +
> +shutdown
> +
> +services --disabled=cloud-init,cloud-init-local,cloud-final,cloud-config,docker-storage-setup
> +
> +# Using ostree repo included in installation iso. Respective ostreesetup command is included here.
> +# The included kickstart file with the command is created during installation iso compose.
> +%include /usr/share/anaconda/interactive-defaults.ks
> +
> +# We copy content of separate /boot partition to root part when building live squashfs image,
> +# and we don't want systemd to try to mount it when pxe booting
> +%post
> +cat /dev/null > /etc/fstab
> +%end
> +
> +
> diff --git a/share/pxe-live/pxe-config.tmpl b/share/pxe-live/pxe-config.tmpl
> new file mode 100644
> index 0000000..6b841bb
> --- /dev/null
> +++ b/share/pxe-live/pxe-config.tmpl
> @@ -0,0 +1,3 @@
> +# PXE configuration template generated by livemedia-creator
> +kernel <PXE_DIR>${kernel}
> +append initrd=<PXE_DIR>${initrd} root=live:<URL>/${liveimg} ${addargs}
> diff --git a/src/pylorax/imgutils.py b/src/pylorax/imgutils.py
> index 57d62fb..da58c17 100644
> --- a/src/pylorax/imgutils.py
> +++ b/src/pylorax/imgutils.py
> @@ -91,6 +91,30 @@ def mksquashfs(rootdir, outfile, compression="default", compressargs=None):
>           compressargs = ["-comp", compression] + compressargs
>       return execWithRedirect("mksquashfs", [rootdir, outfile] + compressargs)
>   
> +def mkrootfsimg(rootdir, outfile, label, size=2, sysroot=""):
> +    """
> +    Make rootfs image from a directory
> +
> +    :param str rootdir: Root directory
> +    :param str outfile: Path of output image file
> +    :param str label: Filesystem label
> +    :param int size: Size of the image, if None computed automatically
> +    :param str sysroot: path to system (deployment) root relative to physical root
> +    """
> +    if size:
> +        fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin'
> +    else:
> +        fssize = None       # Let mkext4img figure out the needed size
> +
> +    mkext4img(rootdir, outfile, label=label, size=fssize)
> +    # Reset selinux context on new rootfs
> +    with LoopDev(outfile) as loopdev:
> +        with Mount(loopdev) as mnt:
> +            cmd = [ "setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/install",
> +                    "/etc/selinux/targeted/contexts/files/file_contexts", "/"]
> +            root = join(mnt, sysroot.lstrip("/"))
> +            runcmd(cmd, root=root)
> +
>   ######## Utility functions ###############################################
>   
>   def mksparse(outfile, size):
> diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py
> index 12eb348..f51980e 100644
> --- a/src/pylorax/treebuilder.py
> +++ b/src/pylorax/treebuilder.py
> @@ -156,20 +156,10 @@ class RuntimeBuilder(object):
>           # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut
>           compressargs = compressargs or []
>           workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir")
> -        if size:
> -            fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin'
> -        else:
> -            fssize = None       # Let mkext4img figure out the needed size
>           os.makedirs(joinpaths(workdir, "LiveOS"))
> -        imgutils.mkext4img(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"),
> -                           label="Anaconda", size=fssize)
> -
> -        # Reset selinux context on new rootfs
> -        with imgutils.LoopDev( joinpaths(workdir, "LiveOS/rootfs.img") ) as loopdev:
> -            with imgutils.Mount(loopdev) as mnt:
> -                cmd = [ "setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/install",
> -                        "/etc/selinux/targeted/contexts/files/file_contexts", "/"]
> -                runcmd(cmd, root=mnt)
> +
> +        imgutils.mkrootfsimg(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"),
> +                             "Anaconda", size=size)
>   
>           # squash the live rootfs and clean up workdir
>           imgutils.mksquashfs(workdir, outfile, compression, compressargs)
> diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator
> index e25d422..8b8cf80 100755
> --- a/src/sbin/livemedia-creator
> +++ b/src/sbin/livemedia-creator
> @@ -37,6 +37,7 @@ import shutil
>   import argparse
>   import hashlib
>   import re
> +import glob
>   
>   # Use pykickstart to calculate disk image size
>   from pykickstart.parser import KickstartParser
> @@ -53,8 +54,8 @@ from pylorax.treebuilder import findkernels
>   from pylorax.sysutils import joinpaths, remove
>   from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
>   from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount
> -from pylorax.imgutils import mksquashfs, mkqcow2, mktar
> -from pylorax.executils import execWithRedirect, execWithCapture
> +from pylorax.imgutils import mksquashfs, mkqcow2, mktar, mkrootfsimg
> +from pylorax.executils import execWithRedirect, execWithCapture, runcmd
>   
>   # no-virt mode doesn't need libvirt, so make it optional
>   try:
> @@ -414,6 +415,22 @@ def is_image_mounted(disk_img):
>                   return True
>       return False
>   
> +def find_ostree_root(phys_root):
> +    """
> +    Find root of ostree deployment
> +
> +    :param str phys_root: Path to physical root
> +    :returns: Relative path of ostree deployment root
> +    :rtype: str
> +    :raise Exception: More than one deployment roots were found
> +    """
> +    ostree_root = ""
> +    ostree_sysroots = glob.glob(joinpaths(phys_root, "ostree/boot.0/*/*/0"))
> +    if ostree_sysroots:
> +        if len(ostree_sysroots) > 1:
> +            raise Exception("Too many deployment roots found: %s" % ostree_sysroots)
> +        ostree_root = os.path.relpath(ostree_sysroots[0], phys_root)
> +    return ostree_root
>   
>   def get_arch(mount_dir):
>       """
> @@ -499,7 +516,6 @@ def make_fsimage(diskimage, fsimage, img_size=None, label="Anaconda"):
>   
>           mkext4img(img_mount.mount_dir, fsimage, size=img_size, label=label)
>   
> -
>   def make_runtime(opts, mount_dir, work_dir):
>       """
>       Make the squashfs image from a directory
> @@ -527,6 +543,99 @@ def make_runtime(opts, mount_dir, work_dir):
>       log.info("Creating runtime")
>       rb.create_runtime(joinpaths(work_dir, RUNTIME), size=None)
>   
> +def rebuild_initrds_for_live(opts, sys_root_dir, results_dir):
> +    """
> +    Rebuild intrds for pxe live image (root=live:http://)
> +
> +    :param opts: options passed to livemedia-creator
> +    :type opts: argparse options
> +    :param str sys_root_dir: Path to root of the system
> +    :param str results_dir: Path of directory for storing results
> +    """
> +    if not opts.dracut_args:
> +        dracut_args = DRACUT_DEFAULT
> +    else:
> +        dracut_args = []
> +        for arg in opts.dracut_args:
> +            dracut_args += arg.split(" ", 1)
> +    log.info("dracut args = {0}".format(dracut_args))
> +
> +    dracut = ["dracut", "--nomdadmconf", "--nolvmconf"] + dracut_args
> +
> +    kdir = "boot"
> +    if opts.ostree:
> +        kernels_dir = glob.glob(joinpaths(sys_root_dir, "boot/ostree/*"))[0]
> +        kdir = os.path.relpath(kernels_dir, sys_root_dir)
> +
> +    kernels = [kernel for kernel in findkernels(sys_root_dir, kdir)
> +               if hasattr(kernel, "initrd")]
> +    if not kernels:
> +        raise Exception("No initrds found, cannot rebuild_initrds")
> +
> +    # Hush some dracut warnings. TODO: bind-mount proc in place?
> +    open(joinpaths(sys_root_dir,"/proc/modules"),"w")
> +
> +    if opts.ostree:
> +        # Dracut assumes to have some dirs in disk image
> +        # /var/tmp for temp files
> +        vartmp_dir = joinpaths(sys_root_dir, "var/tmp")
> +        if not os.path.isdir(vartmp_dir):
> +            os.mkdir(vartmp_dir)
> +        # /root (maybe not fatal)
> +        root_dir = joinpaths(sys_root_dir, "var/roothome")
> +        if not os.path.isdir(root_dir):
> +            os.mkdir(root_dir)
> +        # /tmp (maybe not fatal)
> +        tmp_dir = joinpaths(sys_root_dir, "sysroot/tmp")
> +        if not os.path.isdir(tmp_dir):
> +            os.mkdir(tmp_dir)
> +
> +    for kernel in kernels:
> +        outfile = kernel.initrd.path + ".live"
> +        log.info("rebuilding %s", outfile)
> +
> +        kver = kernel.version
> +
> +        cmd = dracut + [outfile, kver]
> +        runcmd(cmd, root=sys_root_dir)
> +
> +        new_initrd_path = joinpaths(results_dir, os.path.basename(kernel.initrd.path))
> +        shutil.move(joinpaths(sys_root_dir, outfile), new_initrd_path)
> +        shutil.copy2(joinpaths(sys_root_dir, kernel.path), results_dir)
> +
> +    os.unlink(joinpaths(sys_root_dir,"/proc/modules"))
> +
> +def create_pxe_config(template, images_dir, live_image_name, add_args = None):
> +    """
> +    Create template for pxe to live configuration
> +
> +    :param str images_dir: Path of directory with images to be used
> +    :param str live_image_name: Name of live rootfs image file
> +    :param list add_args: Arguments to be added to initrd= pxe config
> +    """
> +
> +    add_args = add_args or []
> +
> +    kernels = [kernel for kernel in findkernels(images_dir, kdir="")
> +               if hasattr(kernel, "initrd")]
> +    if not kernels:
> +        return
> +
> +    kernel = kernels[0]
> +
> +    add_args_str = " ".join(add_args)
> +
> +
> +    try:
> +        result = Template(filename=template).render(kernel=kernel.path,
> +                          initrd=kernel.initrd.path, liveimg=live_image_name,
> +                          addargs=add_args_str)
> +    except Exception:
> +        log.error(text_error_template().render())
> +        raise
> +
> +    with open (joinpaths(images_dir, "PXE_CONFIG"), "w") as f:
> +        f.write(result)
>   
>   def make_livecd(opts, mount_dir, work_dir):
>       """
> @@ -588,6 +697,36 @@ def make_livecd(opts, mount_dir, work_dir):
>   
>       return work_dir
>   
> +def mount_boot_part_over_root(img_mount):
> +    """
> +    Mount boot partition to /boot of root fs mounted in img_mount
> +
> +    Used for OSTree so it finds deployment configurations on live rootfs
> +
> +    param img_mount: object with mounted disk image root partition
> +    type img_mount: imgutils.PartitionMount
> +    """
> +    root_dir = img_mount.mount_dir
> +    is_boot_part = lambda dir: os.path.exists(dir+"/loader.0")
> +    tmp_mount_dir = tempfile.mkdtemp()
> +    sys_root = find_ostree_root(root_dir)
> +    sysroot_boot_dir = None
> +    for dev, _size in img_mount.loop_devices:
> +        if dev is img_mount.mount_dev:
> +            continue
> +        try:
> +            mount("/dev/mapper/"+dev, mnt=tmp_mount_dir)
> +            if is_boot_part(tmp_mount_dir):
> +                umount(tmp_mount_dir)
> +                sysroot_boot_dir = joinpaths(joinpaths(root_dir, sys_root), "boot")
> +                mount("/dev/mapper/"+dev, mnt=sysroot_boot_dir)
> +                break
> +            else:
> +                umount(tmp_mount_dir)
> +        except subprocess.CalledProcessError as e:
> +            log.debug("Looking for boot partition error: %s", e)
> +    remove(tmp_mount_dir)
> +    return sysroot_boot_dir
>   
>   def novirt_install(opts, disk_img, disk_size, repo_url):
>       """
> @@ -840,6 +979,53 @@ def make_image(opts, ks):
>       log.info("Disk Image install successful")
>       return disk_img
>   
> +def make_live_images(opts, work_dir, root_dir, rootfs_image=None):
> +    """
> +    Create live images from direcory or rootfs image
> +
> +    :param opts: options passed to livemedia-creator
> +    :type opts: argparse options
> +    :param str work_dir: Directory for storing results
> +    :param str root_dir: Root directory of live filesystem tree
> +    :param str rootfs_image: Path to live rootfs image to be used
> +    :returns: Path of directory with created images
> +    :rtype: str
> +    """
> +    sys_root = ""
> +    if opts.ostree:
> +        sys_root = find_ostree_root(root_dir)
> +
> +    squashfs_root_dir = joinpaths(work_dir, "squashfs_root")
> +    liveos_dir = joinpaths(squashfs_root_dir, "LiveOS")
> +    os.makedirs(liveos_dir)
> +
> +    if rootfs_image:
> +        rc = execWithRedirect("/bin/ln", [rootfs_image, joinpaths(liveos_dir, "rootfs.img")])
> +        if rc != 0:
> +            shutil.copy2(rootfs_image, joinpaths(liveos_dir, "rootfs.img"))
> +    else:
> +        log.info("Creating live rootfs image")
> +        mkrootfsimg(root_dir, joinpaths(liveos_dir, "rootfs.img"), "LiveOS", size=None, sysroot=sys_root)
> +
> +    log.info("Packing live rootfs image")
> +    add_pxe_args = []
> +    live_image_name = "live-rootfs.squashfs.img"
> +    mksquashfs(squashfs_root_dir,
> +                joinpaths(work_dir, live_image_name),
> +                opts.compression,
> +                opts.compress_args)
> +
> +    remove(squashfs_root_dir)
> +
> +    log.info("Rebuilding initramfs for live")
> +    rebuild_initrds_for_live(opts, joinpaths(root_dir, sys_root), work_dir)
> +
> +    if opts.ostree:
> +        add_pxe_args.append("ostree=/%s" % sys_root)
> +    template = joinpaths(opts.lorax_templates, "pxe-live/pxe-config.tmpl")
> +    create_pxe_config(template, work_dir, live_image_name, add_pxe_args)
> +
> +    return work_dir
>   
>   def setup_logging(opts):
>       """
> @@ -892,6 +1078,10 @@ def main():
>                           help="Build an ami image")
>       action.add_argument("--make-tar", action="store_true",
>                           help="Build a tar of the root filesystem")
> +    action.add_argument("--make-pxe-live", action="store_true",
> +                        help="Build a live pxe boot squashfs image")
> +    action.add_argument("--make-ostree-live", action="store_true",
> +                        help="Build a live pxe boot squashfs image of Atomic Host")
>   
>       parser.add_argument("--iso", type=os.path.abspath,
>                           help="Anaconda installation .iso path to use for virt-install")
> @@ -1091,6 +1281,12 @@ def main():
>       if opts.app_file:
>           opts.app_file = joinpaths(opts.tmp, opts.app_file)
>   
> +    if opts.make_ostree_live:
> +        opts.make_pxe_live = True
> +        opts.ostree = True
> +    else:
> +        opts.ostree = False
> +
>       tempfile.tempdir = opts.tmp
>       disk_img = None
>   
> @@ -1165,6 +1361,31 @@ def main():
>               make_appliance(opts.disk_image or disk_img, opts.app_name,
>                              opts.app_template, opts.app_file, networks, opts.ram,
>                              opts.vcpus, opts.arch, opts.title, opts.project, opts.releasever)
> +        elif opts.make_pxe_live:
> +            work_dir = tempfile.mkdtemp()
> +            log.info("working dir is {0}".format(work_dir))
> +
> +            if (opts.fs_image or opts.no_virt) and not opts.disk_image:
> +                # Create pxe live images from a filesystem image
> +                disk_img = opts.fs_image or disk_img
> +                with Mount(disk_img, opts="loop") as mnt_dir:
> +                    result_dir = make_live_images(opts, work_dir, mnt_dir, rootfs_image=disk_img)
> +            else:
> +                # Create pxe live images from a partitioned disk image
> +                disk_img = opts.disk_image or disk_img
> +                is_root_part = None
> +                if opts.ostree:
> +                    is_root_part = lambda dir: os.path.exists(dir+"/ostree/deploy")
> +                with PartitionMount(disk_img, mount_ok=is_root_part) as img_mount:
> +                    if img_mount and img_mount.mount_dir:
> +                        try:
> +                            mounted_sysroot_boot_dir = None
> +                            if opts.ostree:
> +                                mounted_sysroot_boot_dir = mount_boot_part_over_root(img_mount)
> +                            result_dir = make_live_images(opts, work_dir, img_mount.mount_dir)
> +                        finally:
> +                            if mounted_sysroot_boot_dir:
> +                                umount(mounted_sysroot_boot_dir)
>   
>           if opts.result_dir and result_dir:
>               shutil.copytree(result_dir, opts.result_dir)



More information about the anaconda-patches mailing list