[PATCH 1/2 livemedia-creator atomic] Add --make-pxe-live target and --ostree flag for Atomic Host.

Radek Vykydal rvykydal at redhat.com
Mon Dec 15 15:49:06 UTC 2014


Generate live squashfs and initrd for pxe live install.
Also generate pxe config template.

To be used with 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.

--ostree flag ensures using deployment root instead of physical root of
installed disk image where needed.
---
 docs/rhel-atomic-pxe-live.ks |  39 +++++++
 src/sbin/livemedia-creator   | 238 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 274 insertions(+), 3 deletions(-)
 create mode 100644 docs/rhel-atomic-pxe-live.ks

diff --git a/docs/rhel-atomic-pxe-live.ks b/docs/rhel-atomic-pxe-live.ks
new file mode 100644
index 0000000..324dad2
--- /dev/null
+++ b/docs/rhel-atomic-pxe-live.ks
@@ -0,0 +1,39 @@
+# Kickstart to be used to genrate pxe to live atomic image using --pxe-to-live and --ostree options
+lang en_US.UTF-8
+keyboard us
+timezone America/New_York
+clearpart --all --initlabel
+network --bootproto=dhcp --device=link --activate
+rootpw --plaintext atomic
+
+# 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
+
+# Regenerating initramfs requires dracut-network package in repo, relabeling rootfs requires policycoreutils
+#ostreesetup --osname=rhel-atomic-host --remote=rhel-atomic-host --url=http://10.34.102.90:8000/repo --ref=rhel-atomic-host/7.0-buildmaster/x86_64/standard --nogpg
+ostreesetup --osname=rhel-atomic-host --remote=rhel-atomic-host --url=http://10.34.102.90:8000/repo --ref=rhel-atomic-host/7.0-buildmaster/x86_64/live-selinux --nogpg
+
+# We throw away separate /boot partition 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
+
+# This comes from interactive-defaults.ks of installer.iso with included ostree repo
+%post --erroronfail
+rm -f /etc/ostree/remotes.d/rhel-atomic-host.conf
+%end
+
+# This comes from interactive-defaults.ks of installer.iso with included ostree repo
+%post --erroronfail
+rm -f /etc/ostree/remotes.d/*.conf
+echo 'unconfigured-state=This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.' >> $(ostree admin --print-current-dir).origin
+%end
+
+
+
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator
index e25d422..e25291c 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
@@ -52,9 +53,9 @@ from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape
 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 get_loop_name, dm_detach, mount, umount, Mount, LoopDev
 from pylorax.imgutils import mksquashfs, mkqcow2, mktar
-from pylorax.executils import execWithRedirect, execWithCapture
+from pylorax.executils import execWithRedirect, execWithCapture, runcmd
 
 # no-virt mode doesn't need libvirt, so make it optional
 try:
@@ -414,6 +415,21 @@ 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
+    """
+    ostree_root = ""
+    ostree_sysroots = glob.glob(joinpaths(phys_root, "ostree/boot.0/*/*/0"))
+    if ostree_sysroots:
+        if len(ostree_sysroots) > 1:
+            log.error("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 +515,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 +542,118 @@ def make_runtime(opts, mount_dir, work_dir):
     log.info("Creating runtime")
     rb.create_runtime(joinpaths(work_dir, RUNTIME), size=None)
 
+def make_live_rootfs(mount_dir, image_path, size=2, sys_root=""):
+    """
+    Make rootfs image from a directory
+
+    :param str mount_dir: Root directory
+    :param str image_path: Path of output image file
+    :param int size: Size of the image, if None computed automatically
+    :param str sys_root: 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(mount_dir, image_path, label="LiveOS", size=fssize)
+    # Reset selinux context on new rootfs
+    with LoopDev(image_path) 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 = joinpaths(mnt, sys_root.lstrip("/"))
+            runcmd(cmd, root=root)
+
+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(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)
+
+    buf = """# PXE configuration template generated by livemedia-creator
+kernel <PXE_DIR>%s
+append initrd=<PXE_DIR>%s root=live:<URL>/%s %s
+""" % (kernel.path, kernel.initrd.path, live_image_name, add_args_str)
+    with open (joinpaths(images_dir, "PXE_CONFIG"), "w") as f:
+        f.write(buf)
 
 def make_livecd(opts, mount_dir, work_dir):
     """
@@ -588,6 +715,34 @@ 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)
+    sys_root_bootdir = None
+    for dev, size in img_mount.loop_devices:
+        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 +995,52 @@ 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")
+        make_live_rootfs(root_dir, joinpaths(liveos_dir, "rootfs.img"), size=None, sys_root=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)
+    create_pxe_config(work_dir, live_image_name, add_pxe_args)
+
+    return work_dir
 
 def setup_logging(opts):
     """
@@ -892,6 +1093,8 @@ 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 live in squashfs and initrd for pxe boot")
 
     parser.add_argument("--iso", type=os.path.abspath,
                         help="Anaconda installation .iso path to use for virt-install")
@@ -914,6 +1117,9 @@ def main():
                         help="location of iso directory tree with initrd.img "
                              "and vmlinuz. Used to run virt-install with a "
                              "newer initrd than the iso.")
+    parser.add_argument("--ostree", action="store_true",
+                        help="Ostree (Atomic) installation")
+
 
     parser.add_argument("--logfile", default="./livemedia.log",
                         type=os.path.abspath,
@@ -1165,6 +1371,32 @@ 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:
+
+                        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)
+                        if mounted_sysroot_boot_dir:
+                            umount(mounted_sysroot_boot_dir)
 
         if opts.result_dir and result_dir:
             shutil.copytree(result_dir, opts.result_dir)
-- 
1.9.3



More information about the anaconda-patches mailing list