[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