From 575c78a892746b375cd4931aec4a748e90eae807 Mon Sep 17 00:00:00 2001 From: Jiri Kucera Date: May 25 2022 10:24:10 +0000 Subject: Add test for booting ISO creation Add test that creates bootable ISO image using the command from Red Had Enterprise Linux documentation. --- diff --git a/containers/Containerfile.fedora.testenv b/containers/Containerfile.fedora.testenv index 097b64b..d3cb500 100644 --- a/containers/Containerfile.fedora.testenv +++ b/containers/Containerfile.fedora.testenv @@ -2,6 +2,20 @@ FROM fedora:latest -RUN dnf -y update && \ +RUN echo ==== Updating ==== && \ + dnf -y update && \ + echo ==== Installing bunch of handy utilities ==== && \ + dnf -y install tree which && \ + echo ==== Installing testing frameworks ==== && \ dnf -y install beakerlib && \ + echo ==== Installing tests requirements ==== && \ + dnf -y install genisoimage xorriso \ + findutils grep sed gawk \ + dosfstools mtools \ + gcc kernel-headers glibc-headers-x86 \ + cpio xz \ + kernel-core \ + syslinux-nonlinux \ + shim-x64 shim-ia32 grub2-efi-x64 grub2-efi-ia32 && \ + echo ==== Cleaning dnf cache ==== && \ dnf clean all diff --git a/tests/functional/docs/main.fmf b/tests/functional/docs/main.fmf new file mode 100644 index 0000000..aad4044 --- /dev/null +++ b/tests/functional/docs/main.fmf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: MIT +--- +tag+: + - docs diff --git a/tests/functional/docs/rhel/boot/main.fmf b/tests/functional/docs/rhel/boot/main.fmf new file mode 100644 index 0000000..1139d72 --- /dev/null +++ b/tests/functional/docs/rhel/boot/main.fmf @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MIT +--- +summary: Create bootable ISO +description: | + Test that the genisoimage command when run with parameters as specified in + Red Hat Enterprise Linux documentation is able to produce bootable ISO + images. + +tag+: + - docs-rhel-boot + +component: + - cdrkit + - libisoburn +require: + - genisoimage + - xorriso + - findutils + - grep + - sed + - gawk + - dosfstools + - mtools + - gcc + - kernel-headers + - glibc-headers-x86 + - cpio + - xz + - syslinux-nonlinux diff --git a/tests/functional/docs/rhel/boot/runtest.sh b/tests/functional/docs/rhel/boot/runtest.sh new file mode 100755 index 0000000..efd94b5 --- /dev/null +++ b/tests/functional/docs/rhel/boot/runtest.sh @@ -0,0 +1,556 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT + +set -o pipefail + +T_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +T_TOPDIR="$(cd "${T_HERE}/../../../../.." && pwd -P)" + +. /usr/share/beakerlib/beakerlib.sh || exit 1 +. "${T_TOPDIR}/lib/blutils.sh" || exit 1 + + +blutils_SetTestName "Functional" 3 +blutils_SetTestVersion 1 + +PACKAGES="${PACKAGES:-xorriso}" +REQUIRES="${REQUIRES:- + findutils + grep + sed + gawk + dosfstools + mtools + gcc + kernel-headers + glibc-headers-x86 + cpio + xz + syslinux-nonlinux +}" + +if command -v dnf; then + T_PACKAGER="dnf" +else + T_PACKAGER="yum" +fi + +# Our fake Linux distribution name +T_FAKEDIST_NAME="Fake Linux Distro" +T_VOLID="FLD-1.0" + +# Work space directories and files names +T_ISO="boot.iso" +T_ISODIR="bootiso" +T_INITRDDIR="initrd" +T_INITDIR="init" + +# System files needed to create bootable ISO. These are not present inside +# containers, so we install them from corresponding packages +T_EFI_BOOT_BOOTIA32_EFI="" +T_EFI_BOOT_BOOTX64_EFI="" +T_EFI_BOOT_FONTS_UNICODE_PF2="" +T_EFI_BOOT_GRUBIA32_EFI="" +T_EFI_BOOT_GRUBX64_EFI="" +T_EFI_BOOT_MMIA32_EFI="" +T_EFI_BOOT_MMX64_EFI="" +T_VMLINUZ="" + + +# ============================================================================= +# == Setup + +# ----------------------------------------------------------------------------- +# -- Bootable ISO Disk Data Preparation Routines + +## +# __copy FROM TO [MODE] +# +# Copy FROM to TO, eventually change mode of TO to MODE. +function __copy() { + cp -v "${1}" "${2}" + if [[ "${3:-}" ]]; then + chmod "${3}" "${2}" + fi +} + +## +# __install PACKAGE +# +# Install PACKAGE if it is not yet installed. PACKAGE is removed during the +# cleanup phase. +function __install() { + if rpm -q "${1}" >/dev/null 1>&2; then + rlLog "Package ${1} is already installed" + return + fi + rlRun "${T_PACKAGER} -y install ${1}" 0 "Installing ${1}" || return $? + blutils_AtCleanup "__uninstall ${1}" +} + +## +# __uninstall PACKAGE +# +# Remove PACKAGE from the system. +function __uninstall() { + rlRun "${T_PACKAGER} -y remove ${1}" 0 "Removing ${1}" +} + +## +# __ensure_efi_shim +# +# Ensure shim-{ia32,x64} are present. +function __ensure_efi_shim() { + if [[ -z "${T_EFI_BOOT_BOOTX64_EFI}" ]]; then + __install shim-x64 || return $? + fi + if [[ -z "${T_EFI_BOOT_BOOTIA32_EFI}" ]]; then + __install shim-ia32 + fi +} + +## +# __ensure_grub2_efi +# +# Ensure grub2-efi-{ia32,x64} are present. +function __ensure_grub2_efi() { + if [[ -z "${T_EFI_BOOT_GRUBX64_EFI}" ]]; then + __install grub2-efi-x64 || return $? + fi + if [[ -z "${T_EFI_BOOT_GRUBIA32_EFI}" ]]; then + __install grub2-efi-ia32 + fi +} + +## +# __ensure_kernel_core +# +# Ensure kernel-core is present. +function __ensure_kernel_core() { + if [[ -z "${T_VMLINUZ}" ]]; then + __install kernel-core + fi +} + +## +# __inspect_boot +# +# Look for files we need to produce a bootable ISO. +function __inspect_boot() { + while read -r line; do + case "${line}" in + */BOOTIA32.EFI) + T_EFI_BOOT_BOOTIA32_EFI="${T_EFI_BOOT_BOOTIA32_EFI:-${line}}" + ;; + */BOOTX64.EFI) + T_EFI_BOOT_BOOTX64_EFI="${T_EFI_BOOT_BOOTX64_EFI:-${line}}" + ;; + */fonts/unicode.pf2) + T_EFI_BOOT_FONTS_UNICODE_PF2="${T_EFI_BOOT_FONTS_UNICODE_PF2:-${line}}" + ;; + */grubia32.efi) + T_EFI_BOOT_GRUBIA32_EFI="${T_EFI_BOOT_GRUBIA32_EFI:-${line}}" + ;; + */grubx64.efi) + T_EFI_BOOT_GRUBX64_EFI="${T_EFI_BOOT_GRUBX64_EFI:-${line}}" + ;; + */mmia32.efi) + T_EFI_BOOT_MMIA32_EFI="${T_EFI_BOOT_MMIA32_EFI:-${line}}" + ;; + */mmx64.efi) + T_EFI_BOOT_MMX64_EFI="${T_EFI_BOOT_MMX64_EFI:-${line}}" + ;; + esac + done < <(find /boot -print) +} + +## +# __find_vmlinuz +# +# Look for vmlinuz. +function __find_vmlinuz() { + if [[ ! -d /lib/modules ]] || [[ "${T_VMLINUZ}" ]]; then + return + fi + + while read -r line; do + T_VMLINUZ="${line}" + done < <(find /lib/modules -name vmlinuz -type f -print | sort) +} + +## +# __prereq VALUE NAME +# +# Check whether prerequisite NAME is present on the system by checking that +# VALUE is non-empty. +function __prereq() { + if [[ -z "${1}" ]]; then + echo "${2} not found on the system." >&2 + return 1 + fi +} + +## +# __check_prerequisites +# +# Check we have all prerequisites to make a bootable ISO. If there are missing +# ones try to install them. +function __check_prerequisites() { + # Inspect /boot + __inspect_boot || return $? + # Try to install missing packages to get required files. Normally, this + # should be not necessary but containers have /boot empty + __ensure_efi_shim || return $? + __ensure_grub2_efi || return $? + # Inspect /boot again to update unset variables + __inspect_boot || return $? + + # Try to find vmlinuz + __find_vmlinuz || return $? + # Install kernel-core of which vmlinuz is a part of (this step is also + # unnecessary unless if we are inside a container) + __ensure_kernel_core || return $? + # Update T_VMLINUZ with path to vmlinuz + __find_vmlinuz || return $? + + # Check we have all what we need to make an ISO bootable + __prereq "${T_EFI_BOOT_BOOTX64_EFI}" BOOTX64.EFI && \ + __prereq "${T_EFI_BOOT_BOOTIA32_EFI}" BOOTIA32.EFI && \ + __prereq "${T_EFI_BOOT_FONTS_UNICODE_PF2}" unicode.pf2 && \ + __prereq "${T_EFI_BOOT_GRUBX64_EFI}" grubx64.efi && \ + __prereq "${T_EFI_BOOT_GRUBIA32_EFI}" grubia32.efi && \ + __prereq "${T_EFI_BOOT_MMX64_EFI}" mmx64.efi && \ + __prereq "${T_EFI_BOOT_MMIA32_EFI}" mmia32.efi && \ + __prereq "${T_VMLINUZ}" vmlinuz +} + +## +# __create_trans_tbl_file DIRECTORY +# +# Create TRANS.TBL file for the DIRECTORY. +function __create_trans_tbl_file() { + ( + cd "${1}" && \ + rm -f TRANS.TBL && \ + find . -maxdepth 1 -print | sort | grep '^\./' | sed -e 's,^\./,,' | awk ' + { + item = orig_item = $0 + + # ISO 9660 Level 1 file names: + # * base names have length up to 8 characters + # * extensions have length up to 3 characters + # * both base name and extension must contain only digits (0 to 9), + # upper-case letters (A to Z) and underscore (_) character + # * base name and extension are separated with dot (.) + + # Convert forbidden characters to _ (redundant dots are converted + # later) + gsub(/[^.0-9A-Za-z_]/, "_", item) + # Upper-case the letters + toupper(item) + + # Split the file name to its base name and extension + basename = item + sub(/[.][^.]+$/, "", basename) + ext = substr(item, length(basename) + 2) + # Handle the ".foo" case + if (basename == "" && ext != "") { + basename = "." ext + ext = "" + } + # Now convert the dots in base name to underscores + gsub(/[.]/, "_", basename) + + # Sanitize base name and extension + basename = substr(basename, 1, 8) + ext = substr(ext, 1, 3) + + # Construct a file name and save its occurrences + filename = basename + if (ext != "") { + filename = filename "." ext + } + occurrences[filename]++ + + # Sanitize collisions (exit when collision is detected - this should be + # sufficient for our use case) + if (occurrences[filename] > 1) { + print "Collision: " filename > "/dev/fd/2" + exit 1 + } + + # Emit the item + isdir = (system("test -d \"./" orig_item "\"") == 0) + printf("%s %-19s %s\n", + isdir ? "D" : "F", + isdir ? filename : filename ";1", + orig_item \ + ) + } + ' >> TRANS.TBL && \ + chmod 0444 TRANS.TBL + ) +} + +## +# __create_boot_disk_tree ISODIR +# +# Initialize a structure of bootable ISO disk data ISODIR. +function __create_boot_disk_tree() { + mkdir -vp "${1}/EFI/BOOT/fonts" && \ + mkdir -vp "${1}/images/pxeboot" && \ + mkdir -vp "${1}/isolinux" +} + +## +# __create_boot_conf CONF [MODE] +# +# Create a simple boot configuration file named CONF. Set CONF mode to MODE if +# needed. +function __create_boot_conf() { + { + cat > "${1}" <<-__EOF__ + set default="1" + set timeout=5 + + menuentry "Run ${T_FAKEDIST_NAME}" { + linuxefi /images/pxeboot/vmlinuz + initrdefi /images/pxeboot/initrd.img + } + __EOF__ + } || return $? + if [[ "${2:-}" ]]; then + chmod "${2}" "${1}" + fi +} + +## +# __create_grub_conf CONF [MODE] +# +# Create a simple GRUB configuration file named CONF. Set CONF mode to MODE if +# needed. +function __create_grub_conf() { + { + cat > "${1}" <<-__EOF__ + default=1 + timeout 5 + + hiddenmenu + title Run ${T_FAKEDIST_NAME} + findiso + kernel @KERNELPATH@ @ROOT@ + initrd @INITRDPATH@ + __EOF__ + } || return $? + if [[ "${2:-}" ]]; then + chmod "${2}" "${1}" + fi +} + +## +# __create_isolinux_cfg CONF [MODE] +# +# Create a simple isolinux configuration file named CONF. Set CONF mode to MODE +# if needed. +function __create_isolinux_cfg() { + { + cat > "${1}" <<-__EOF__ + default linux + + label linux + kernel vmlinuz + append initrd=initrd.img + __EOF__ + } || return $? + if [[ "${2:-}" ]]; then + chmod "${2}" "${1}" + fi +} + +## +# __copy_efi_files ISODIR +# +# Copy EFI files for our boot ISO to the appropriate place in ISODIR. +function __copy_efi_files() { + __create_boot_conf "${1}/EFI/BOOT/BOOT.conf" 0644 && \ + __copy "${T_EFI_BOOT_BOOTIA32_EFI}" "${1}/EFI/BOOT" 0700 && \ + __copy "${T_EFI_BOOT_BOOTX64_EFI}" "${1}/EFI/BOOT" 0700 && \ + __copy "${T_EFI_BOOT_FONTS_UNICODE_PF2}" "${1}/EFI/BOOT/fonts" 0700 && \ + __create_boot_conf "${1}/EFI/BOOT/grub.cfg" 0644 && \ + __copy "${T_EFI_BOOT_GRUBIA32_EFI}" "${1}/EFI/BOOT" 0700 && \ + __copy "${T_EFI_BOOT_GRUBX64_EFI}" "${1}/EFI/BOOT" 0700 && \ + __copy "${T_EFI_BOOT_MMIA32_EFI}" "${1}/EFI/BOOT" 0700 && \ + __copy "${T_EFI_BOOT_MMX64_EFI}" "${1}/EFI/BOOT" 0700 && \ + chmod 2775 "${1}/EFI/BOOT/fonts" && \ + chmod 2775 "${1}/EFI/BOOT" && \ + chmod 0555 "${1}/EFI" +} + +## +# __create_efiboot_image ISODIR +# +# Create efiboot.img in the appropriate place in ISODIR. +function __create_efiboot_image() { + local image="${1}/images/efiboot.img" + local files=( + BOOT.conf BOOTX64.EFI BOOTIA32.EFI fonts/unicode.pf2 + grub.cfg grubx64.efi grubia32.efi mmx64.efi mmia32.efi + ) + + dd if=/dev/zero of="${image}" bs=1M count=10 && \ + mkfs.fat -F 16 -n "EFIBOOT" "${image}" && \ + mmd -i "${image}" ::EFI && \ + mmd -i "${image}" ::EFI/BOOT && \ + mmd -i "${image}" ::EFI/BOOT/fonts || return $? + + for f in "${files[@]}"; do + mcopy -i "${image}" "${1}/EFI/BOOT/${f}" "::EFI/BOOT/${f}" || return $? + done +} + +## +# __create_init DIRECTORY +# +# Create init binary in a DIRECTORY. Our implementation of init is pretty dumb. +# It prints a message to the console, wait 5 seconds and then shuts down the +# machine. +# +# To keep initramfs as small as possible, we need to get rid off of glibc, not +# mentioning systemd with its army of libraries. +function __create_init() { + mkdir -vp "${1}" && \ + { + cat > "${1}/init.S" <<-__EOF__ + #include + #include + + .global _start + + .section .text + _start: + /* Print the message */ + mov \$SYS_write, %rax + mov \$1, %rdi /* 1 = STDOUT */ + mov \$message, %rsi + mov \$message_len, %rdx + syscall + + /* Sleep for 5 seconds */ + mov \$SYS_nanosleep, %rax + mov \$delay, %rdi + mov \$0, %rsi + syscall + + /* Shut down the machine */ + mov \$SYS_reboot, %rax + mov \$LINUX_REBOOT_MAGIC1, %rdi + mov \$LINUX_REBOOT_MAGIC2, %rsi + mov \$LINUX_REBOOT_CMD_POWER_OFF, %rdx + syscall + + /* We can get here if the reboot syscall was unsuccessful. In this + case, just exit with 1 and give up + */ + mov \$SYS_exit, %rax + mov \$1, %rdi + syscall + + .section .data + message: + .ascii "Hello from ${T_FAKEDIST_NAME}!" + .byte 32 + .ascii "Power off for 5 seconds." + .byte 10 + .equ message_len, .-message + delay: + .quad 5, 0 + __EOF__ + } && \ + (cd "${1}" && gcc -nostdlib -static -s -o init init.S) +} + +## +# __create_initramfs ISODIR INITRDDIR INITDIR +# +# Create a minimal initrd.img and put it to appropriate place in ISODIR. +# INITRDDIR is a work space to prepare initramfs and INITDIR is a work space to +# prepare init binary. +function __create_initramfs() { + mkdir -vp "${2}" && \ + __create_init "${3}" && \ + __copy "${3}/init" "${2}" 0777 && \ + ( + cd "${2}" && find . -print | cpio -c -o | xz --check=crc32 + ) > "${1}/images/pxeboot/initrd.img" && \ + chmod 0644 "${1}/images/pxeboot/initrd.img" +} + +## +# __copy_isolinux_files ISODIR +# +# Copy isolinux files to ISODIR/isolinux location. +function __copy_isolinux_files() { + # Create fake boot catalog to include it inside TRANS.TBL. Proper boot + # catalog will be created by xorriso/mkisofs + touch "${1}/isolinux/boot.cat" && \ + chmod 0444 "${1}/isolinux/boot.cat" && \ + __create_grub_conf "${1}/isolinux/grub.conf" 0644 && \ + __copy "${1}/images/pxeboot/initrd.img" "${1}/isolinux" && \ + __copy /usr/share/syslinux/isolinux.bin "${1}/isolinux" 0644 && \ + __create_isolinux_cfg "${1}/isolinux/isolinux.cfg" 0644 && \ + __copy /usr/share/syslinux/ldlinux.c32 "${1}/isolinux" 0644 && \ + __copy "${T_VMLINUZ}" "${1}/isolinux" 0755 && \ + chmod 2775 "${1}/isolinux" +} + +## +# __create_boot_disk_data DIRECTORY +# +# Prepare data inside DIRECTORY for creating bootable ISO image. +function __create_boot_disk_data() { + local bootisowd="${1}/${T_ISODIR}" + local initrdwd="${1}/${T_INITRDDIR}" + local initwd="${1}/${T_INITDIR}" + + __check_prerequisites && \ + __create_boot_disk_tree "${bootisowd}" && \ + __copy_efi_files "${bootisowd}" && \ + __create_efiboot_image "${bootisowd}" && \ + __create_initramfs "${bootisowd}" "${initrdwd}" "${initwd}" && \ + __copy "${T_VMLINUZ}" "${bootisowd}/images/pxeboot" 0755 && \ + chmod 2775 "${bootisowd}/images/pxeboot" && \ + chmod 0555 "${bootisowd}/images" && \ + __copy_isolinux_files "${bootisowd}" && \ + __create_trans_tbl_file "${bootisowd}/EFI/BOOT" && \ + __create_trans_tbl_file "${bootisowd}/EFI/BOOT/fonts" && \ + __create_trans_tbl_file "${bootisowd}/images" && \ + __create_trans_tbl_file "${bootisowd}/images/pxeboot" && \ + __create_trans_tbl_file "${bootisowd}/isolinux" +} + +# ----------------------------------------------------------------------------- +# -- Setup Routine + +function Setup() { + rlAssertRpm --all || return $? + blutils_MakeTmpDir || return $? + blutils_PushTmpDir || return $? + rlRun "__create_boot_disk_data \"${PWD}\"" +} + +# ============================================================================= +# == Tests + +function test_create_bootable_iso_image() { + rlRun "(cd \"${T_ISODIR}\" && genisoimage \ + -U -r -v -T -J -joliet-long \ + -V \"${T_VOLID}\" -volset \"${T_VOLID}\" -A \"${T_VOLID}\" \ + -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \ + -boot-load-size 4 -boot-info-table -eltorito-alt-boot \ + -e images/efiboot.img -no-emul-boot -o ../${T_ISO} . \ + )" 0 "Creating ${T_ISO}" || return $? +} +blutils_AddTest test_create_bootable_iso_image + + +blutils_RunTest diff --git a/tests/functional/docs/rhel/main.fmf b/tests/functional/docs/rhel/main.fmf new file mode 100644 index 0000000..f431e80 --- /dev/null +++ b/tests/functional/docs/rhel/main.fmf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: MIT +--- +tag+: + - docs-rhel diff --git a/tests/functional/main.fmf b/tests/functional/main.fmf new file mode 100644 index 0000000..5d97fdf --- /dev/null +++ b/tests/functional/main.fmf @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MIT +--- +tag: + - CI-Tier-1 + - NoRHEL4 + - NoRHEL5 + - NoRHEL6 + - Tier1 +tier: '1'