Add --make-pxe-live and --make-ostree-live (for Atomic) targets.
--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.
This commit is contained in:
parent
58071fc935
commit
a19c509430
@ -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
|
||||
------------------
|
||||
|
@ -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]
|
||||
@ -70,6 +70,14 @@ Build an ami image
|
||||
\fB\-\-make\-tar\fR
|
||||
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
|
||||
|
26
docs/rhel-atomic-pxe-live.ks
Normal file
26
docs/rhel-atomic-pxe-live.ks
Normal file
@ -0,0 +1,26 @@
|
||||
# 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
|
3
share/pxe-live/pxe-config.tmpl
Normal file
3
share/pxe-live/pxe-config.tmpl
Normal file
@ -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}
|
@ -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):
|
||||
|
@ -160,20 +160,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)
|
||||
|
@ -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
|
||||
@ -54,8 +55,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:
|
||||
@ -420,6 +421,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):
|
||||
"""
|
||||
@ -533,6 +550,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):
|
||||
"""
|
||||
@ -594,6 +704,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_log_check(log_check, proc):
|
||||
"""
|
||||
@ -892,6 +1032,55 @@ def make_image(opts, ks):
|
||||
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):
|
||||
"""
|
||||
Setup the various logs
|
||||
@ -943,6 +1132,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")
|
||||
@ -1142,6 +1335,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
|
||||
|
||||
@ -1216,6 +1415,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)
|
||||
|
Loading…
Reference in New Issue
Block a user