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)
Similar to --make-pxe-live target but rootfs is compressed tarball instead of squashfs image --- src/sbin/livemedia-creator | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator index e25291c..67b25e9 100755 --- a/src/sbin/livemedia-creator +++ b/src/sbin/livemedia-creator @@ -1025,12 +1025,22 @@ def make_live_images(opts, work_dir, root_dir, rootfs_image=None):
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) - + if opts.make_pxe_live: + live_image_name = "live-rootfs.squashfs.img" + mksquashfs(squashfs_root_dir, + joinpaths(work_dir, live_image_name), + opts.compression, + opts.compress_args) + elif opts.make_pxe_live_tar: + live_image_name = "live-rootfs.tar.xz" + compress_args = [] + if opts.compression == "xz" and not opts.compress_args: + compress_args = ["-9"] + rc = mktar(liveos_dir, + joinpaths(work_dir, live_image_name), + opts.compression, + compress_args) + add_pxe_args = ["rd.writable.fsimg"] remove(squashfs_root_dir)
log.info("Rebuilding initramfs for live") @@ -1095,6 +1105,8 @@ def main(): 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") + action.add_argument("--make-pxe-live-tar", action="store_true", + help="Build live in tar.gz and initrd for pxe boot")
parser.add_argument("--iso", type=os.path.abspath, help="Anaconda installation .iso path to use for virt-install") @@ -1371,7 +1383,7 @@ 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: + elif opts.make_pxe_live or opts.make_pxe_live_tar:
work_dir = tempfile.mkdtemp() log.info("working dir is {0}".format(work_dir))
On Mon, Dec 15, 2014 at 04:49:06PM +0100, Radek Vykydal wrote:
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
Thanks for working on this, I have a few comments inline.
We should add a section on using this to the README.livemedia-creator file, and add the new options to the livemedia-creator.1 manpage
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
The IP should point to a working repo if available, be fedora specific for master and rhel specific for the rhel branch. Or use some obvious placeholder for the user to set themselves.
[SNIP]
+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
If it is an error we should raise and exit, if not then call it a warning and log which root it is using. Also, use the log.warning(string, args) form to make pylint happy.
+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)
This is really similar to RuntimeBuilder.create_runtime, instead of duplicating code I'd rather see a new utility method made that can be shared between create_runtime and where you're calling this in make_live_images. I'd leave the squashfs bit in create_runtime and move everything above that to src/pylorax/imgutils.py
+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
- """
I wish we could keep this kind of thing out of lmc, but it is different enough from the normal initrd recreation in pylorax that I guess it can't be avoided.
[SNIP]
- for kernel in kernels:
No need for the blank line ^^
+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)
This should be handled using Mako templates, like I did with the appliance template in make_appliance with libvirt.tmpl. That way users can override the content without having to change the source. I'd put it into share/pxe-live/pxe.tmpl
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
Should be sysroot_boot_dir
- for dev, size in img_mount.loop_devices:
Should use _size to make pylint happy about the unused variable.
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)breakelse:umount(tmp_mount_dir)except subprocess.CalledProcessError as e:log.debug("Looking for boot partition error: %s", e)
Failure should probably raise an error, shouldn't it?
- remove(tmp_mount_dir)
- return sysroot_boot_dir
It is possible that /boot may already exist on / and not be a separate partition. This should be checked for either here, or before calling it (maybe by looking at the kickstart for a /boot mountpoint)
I think it should skip the already mounted / partition to prevent any potential issue with double mounting/unmounting it.
[SNIP]
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")
"Build a live pxe boot squashfs image"
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")
Currently ostree is a variation on pxe-live, does it make sense to have it alter other --make-* targets? Or would a --make-ostree-live switch make more sense (one that just turns on make-pxe-live and ostree)?
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:
No need for extra blank line ^^
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 imagedisk_img = opts.fs_image or disk_imgwith 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 imagedisk_img = opts.disk_image or disk_imgis_root_part = Noneif 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:
No need for extra blank line ^^
mounted_sysroot_boot_dir = Noneif 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)
Wrap this bit in a try: finally with the umount in the finally part so that if things fail it doesn't leave things partially mounted.
Also note that 'make check' will run pylint on the codebase, it shouldn't return any errors or warnings.
On 12/18/2014 09:05 PM, Brian C. Lane wrote:
On Mon, Dec 15, 2014 at 04:49:06PM +0100, Radek Vykydal wrote:
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
Thanks for working on this, I have a few comments inline.
Thanks for the review.
We should add a section on using this to the README.livemedia-creator file, and add the new options to the livemedia-creator.1 manpage
Okay, I'll come with patches.
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
The IP should point to a working repo if available, be fedora specific for master and rhel specific for the rhel branch. Or use some obvious placeholder for the user to set themselves.
I think I'll go with a placeholder for now.
[SNIP]
+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
If it is an error we should raise and exit, if not then call it a warning and log which root it is using. Also, use the log.warning(string, args) form to make pylint happy.
It should be an error I'll raise and exit.
+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)This is really similar to RuntimeBuilder.create_runtime, instead of duplicating code I'd rather see a new utility method made that can be shared between create_runtime and where you're calling this in make_live_images. I'd leave the squashfs bit in create_runtime and move everything above that to src/pylorax/imgutils.py
Sure (the patch was a bit raw yet, I wanted to know if the approach is acceptable at all).
+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
- """
I wish we could keep this kind of thing out of lmc, but it is different enough from the normal initrd recreation in pylorax that I guess it can't be avoided.
Yeah, consolidating with content of rebuild_initrds seemed not to be doable in any sane way to me.
[SNIP]
- for kernel in kernels:
No need for the blank line ^^
Sure.
+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)This should be handled using Mako templates, like I did with the appliance template in make_appliance with libvirt.tmpl. That way users can override the content without having to change the source. I'd put it into share/pxe-live/pxe.tmpl
I'll look at it.
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
Should be sysroot_boot_dir
Yup.
- for dev, size in img_mount.loop_devices:
Should use _size to make pylint happy about the unused variable.
Okay.
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)breakelse:umount(tmp_mount_dir)except subprocess.CalledProcessError as e:log.debug("Looking for boot partition error: %s", e)Failure should probably raise an error, shouldn't it?
I followed non-fatality of the error from PartitionMount._enter assuming there is a reason for it but not really thinking about it much.
- remove(tmp_mount_dir)
- return sysroot_boot_dir
It is possible that /boot may already exist on / and not be a separate partition. This should be checked for either here, or before calling it (maybe by looking at the kickstart for a /boot mountpoint)
One thing here is that in ostree/atomic installs there is /boot on root partition but it contains data (initrd, kernel) irrelevant for ostree/atomic. But if we exclude root partition as proposed just below, finding (ostree specific) boot partition should be enough to use it and checking kickstart is not necessary (also I imagine Atomic having separate boot as autopart default where we wouldn't find /boot in ks (well this imaginary case may hit some ks checks in lmc))
I think it should skip the already mounted / partition to prevent any potential issue with double mounting/unmounting it.
Good, catch, I'll add the check.
[SNIP]
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")"Build a live pxe boot squashfs image"
Will apply.
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")Currently ostree is a variation on pxe-live, does it make sense to have it alter other --make-* targets? Or would a --make-ostree-live switch make more sense (one that just turns on make-pxe-live and ostree)?
I think --make-ostree-live is really better. I don't expect --ostree to be combined with other --make-* targets.
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:No need for extra blank line ^^
Okay.
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 imagedisk_img = opts.fs_image or disk_imgwith 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 imagedisk_img = opts.disk_image or disk_imgis_root_part = Noneif 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:No need for extra blank line ^^
mounted_sysroot_boot_dir = Noneif 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)Wrap this bit in a try: finally with the umount in the finally part so that if things fail it doesn't leave things partially mounted.
Good idea.
Also note that 'make check' will run pylint on the codebase, it shouldn't return any errors or warnings.
I'll use it.
anaconda-patches@lists.fedorahosted.org