diff --git a/crates/tests-integration/src/install.rs b/crates/tests-integration/src/install.rs index de3a8dbe..dc9d2afd 100644 --- a/crates/tests-integration/src/install.rs +++ b/crates/tests-integration/src/install.rs @@ -93,153 +93,6 @@ pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments) cmd!(sh, "sudo {BASE_ARGS...} -v {tmpdisk}:/disk {image} bootc install to-disk --via-loopback /disk").run()?; Ok(()) }), - Trial::test( - "install to-filesystem with separate /var mount", - move || { - let sh = &xshell::Shell::new()?; - reset_root(sh, image)?; - - // Clean up any leftover LVM state from previous failed runs - let _ = cmd!(sh, "sudo vgchange -an BL").ignore_status().run(); - let _ = cmd!(sh, "sudo vgremove -f BL").ignore_status().run(); - - // Create work directory for the test - let tmpd = sh.create_temp_dir()?; - let work_dir = tmpd.path(); - - // Create a disk image with partitions for root and var - let disk_img = work_dir.join("disk.img"); - let size = 1024 * 1024 * 1024; // 1 GiB - let disk_file = std::fs::File::create(&disk_img)?; - disk_file.set_len(size)?; - drop(disk_file); - - // Setup loop device - let loop_dev = cmd!(sh, "sudo losetup -f --show {disk_img}") - .read()? - .trim() - .to_string(); - - // Helper closure for cleanup - let cleanup = |sh: &Shell, loop_dev: &str, target: &str| { - // Unmount filesystems - let _ = cmd!(sh, "sudo umount -R {target}").ignore_status().run(); - // Deactivate LVM - let _ = cmd!(sh, "sudo vgchange -an BL").ignore_status().run(); - let _ = cmd!(sh, "sudo vgremove -f BL").ignore_status().run(); - // Detach loop device - let _ = cmd!(sh, "sudo losetup -d {loop_dev}").ignore_status().run(); - }; - - // Create partition table - if let Err(e) = (|| -> Result<()> { - cmd!(sh, "sudo parted -s {loop_dev} mklabel gpt").run()?; - // Create BIOS boot partition (for GRUB on GPT) - cmd!(sh, "sudo parted -s {loop_dev} mkpart primary 1MiB 2MiB").run()?; - cmd!(sh, "sudo parted -s {loop_dev} set 1 bios_grub on").run()?; - // Create EFI partition - cmd!( - sh, - "sudo parted -s {loop_dev} mkpart primary fat32 2MiB 202MiB" - ) - .run()?; - cmd!(sh, "sudo parted -s {loop_dev} set 2 esp on").run()?; - // Create boot partition - cmd!( - sh, - "sudo parted -s {loop_dev} mkpart primary ext4 202MiB 1226MiB" - ) - .run()?; - // Create LVM partition - cmd!(sh, "sudo parted -s {loop_dev} mkpart primary 1226MiB 100%").run()?; - - // Reload partition table - cmd!(sh, "sudo partprobe {loop_dev}").run()?; - std::thread::sleep(std::time::Duration::from_secs(2)); - - let efi_part2 = format!("{}p2", loop_dev); // EFI - let root_part3 = format!("{}p3", loop_dev); // Boot - let loop_part4 = format!("{}p4", loop_dev); // LVM - - // Create filesystems on boot partitions - cmd!(sh, "sudo mkfs.vfat -F32 {efi_part2}").run()?; - cmd!(sh, "sudo mkfs.ext4 -F {root_part3}").run()?; - - // Setup LVM - cmd!(sh, "sudo pvcreate {loop_part4}").run()?; - cmd!(sh, "sudo vgcreate BL {loop_part4}").run()?; - - // Create logical volumes - cmd!(sh, "sudo lvcreate -L 100M -n var02 BL").run()?; - cmd!(sh, "sudo lvcreate -L 100M -n root02 BL").run()?; - - // Create filesystems on logical volumes - cmd!(sh, "sudo mkfs.ext4 -F /dev/BL/var02").run()?; - cmd!(sh, "sudo mkfs.ext4 -F /dev/BL/root02").run()?; - - // Get UUIDs - let root_uuid = cmd!(sh, "sudo blkid -s UUID -o value /dev/BL/root02") - .read()? - .trim() - .to_string(); - let boot_uuid = cmd!(sh, "sudo blkid -s UUID -o value {efi_part2}") - .read()? - .trim() - .to_string(); - - // Mount the partitions - let target_dir = work_dir.join("target"); - std::fs::create_dir_all(&target_dir)?; - let target = target_dir.to_str().unwrap(); - - cmd!(sh, "sudo mount /dev/BL/root02 {target}").run()?; - cmd!(sh, "sudo mkdir -p {target}/boot").run()?; - cmd!(sh, "sudo mount {root_part3} {target}/boot").run()?; - cmd!(sh, "sudo mkdir -p {target}/boot/efi").run()?; - cmd!(sh, "sudo mount {efi_part2} {target}/boot/efi").run()?; - - // Create EFI directory structure with some files (simulating existing EFI content) - // This tests that bootc correctly handles /boot/efi as a mount point - cmd!(sh, "sudo mkdir -p {target}/boot/efi/EFI/fedora").run()?; - cmd!(sh, "sudo touch {target}/boot/efi/EFI/fedora/shimx64.efi").run()?; - cmd!(sh, "sudo touch {target}/boot/efi/EFI/fedora/grubx64.efi").run()?; - - // Critical: Mount /var as a separate partition - cmd!(sh, "sudo mkdir -p {target}/var").run()?; - cmd!(sh, "sudo mount /dev/BL/var02 {target}/var").run()?; - - // Run bootc install to-filesystem - // This should succeed and handle the separate /var mount correctly - // Mount the target at /target inside the container for simplicity - cmd!( - sh, - "sudo {BASE_ARGS...} -v {target}:/target -v /dev:/dev {image} bootc install to-filesystem --karg=root=UUID={root_uuid} --root-mount-spec=UUID={root_uuid} --boot-mount-spec=UUID={boot_uuid} /target" - ) - .run()?; - - // Verify the installation succeeded - // Check that bootc created the necessary files - cmd!(sh, "sudo test -d {target}/ostree").run()?; - cmd!(sh, "sudo test -d {target}/ostree/repo").run()?; - // Verify bootloader was installed - cmd!(sh, "sudo test -d {target}/boot/grub2").run()?; - - Ok(()) - })() { - let target = work_dir.join("target"); - let target_str = target.to_str().unwrap(); - cleanup(sh, &loop_dev, target_str); - return Err(e.into()); - } - - // Clean up on success - let target = work_dir.join("target"); - let target_str = target.to_str().unwrap(); - cleanup(sh, &loop_dev, target_str); - - Ok(()) - }, - ), Trial::test( "replace=alongside with ssh keys and a karg, and SELinux disabled", move || { diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index 6f9117a0..365e6618 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -18,6 +18,11 @@ prepare: - expect - ansible-core - zstd + # Required for test-32-install-to-filesystem-var-mount + - parted + - lvm2 + - dosfstools + - e2fsprogs when: running_env != image_mode - how: shell order: 98 @@ -147,4 +152,11 @@ execute: how: fmf test: - /tmt/tests/tests/test-31-switch-to-unified + +/plan-32-install-to-filesystem-var-mount: + summary: Test bootc install to-filesystem with separate /var mount + discover: + how: fmf + test: + - /tmt/tests/tests/test-32-install-to-filesystem-var-mount # END GENERATED PLANS diff --git a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh new file mode 100644 index 00000000..be9dd1d7 --- /dev/null +++ b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh @@ -0,0 +1,148 @@ +# number: 32 +# tmt: +# summary: Test bootc install to-filesystem with separate /var mount +# duration: 30m +# require: +# - parted +# - lvm2 +# - dosfstools +# - e2fsprogs +# +#!/bin/bash +# Test bootc install to-filesystem with a pre-existing /var mount point. +# This verifies that bootc correctly handles scenarios where /var is on a +# separate filesystem, which is a common production setup for managing +# persistent data separately from the OS. + +set -xeuo pipefail + +# Use a generic target image to test skew between the bootc binary doing +# the install and the target image +TARGET_IMAGE="docker://quay.io/centos-bootc/centos-bootc:stream10" + +echo "Testing bootc install to-filesystem with separate /var mount" + +# Disable SELinux enforcement for the install +setenforce 0 + +# Enable usr-overlay to allow modifications +bootc usr-overlay + +# Install required packages (bootc images are immutable, so we need to install +# after usr-overlay is enabled) +dnf install -y parted lvm2 dosfstools e2fsprogs + +# Mask off conflicting ostree state +if test -d /sysroot/ostree; then + mount --bind /usr/share/empty /sysroot/ostree +fi +rm -vrf /usr/lib/bootupd/updates +rm -vrf /usr/lib/bootc/bound-images.d + +# Create a 12GB sparse disk image in /var/tmp (not /tmp which may be tmpfs) +DISK_IMG=/var/tmp/disk-var-mount-test.img +truncate -s 12G "$DISK_IMG" + +# Setup loop device +LOOP_DEV=$(losetup -f --show "$DISK_IMG") +echo "Using loop device: $LOOP_DEV" + +# Cleanup function +cleanup() { + set +e + echo "Cleaning up..." + umount -R /var/mnt/target 2>/dev/null + vgchange -an BL 2>/dev/null + vgremove -f BL 2>/dev/null + losetup -d "$LOOP_DEV" 2>/dev/null + rm -f "$DISK_IMG" 2>/dev/null +} +trap cleanup EXIT + +# Create partition table +parted -s "$LOOP_DEV" mklabel gpt +# BIOS boot partition (for GRUB on GPT) +parted -s "$LOOP_DEV" mkpart primary 1MiB 2MiB +parted -s "$LOOP_DEV" set 1 bios_grub on +# EFI partition (200 MiB) +parted -s "$LOOP_DEV" mkpart primary fat32 2MiB 202MiB +parted -s "$LOOP_DEV" set 2 esp on +# Boot partition (1 GiB) +parted -s "$LOOP_DEV" mkpart primary ext4 202MiB 1226MiB +# LVM partition (rest of disk) +parted -s "$LOOP_DEV" mkpart primary 1226MiB 100% + +# Reload partition table +partprobe "$LOOP_DEV" +sleep 2 + +# Partition device names +EFI_PART="${LOOP_DEV}p2" +BOOT_PART="${LOOP_DEV}p3" +LVM_PART="${LOOP_DEV}p4" + +# Create filesystems on boot partitions +mkfs.vfat -F32 "$EFI_PART" +mkfs.ext4 -F "$BOOT_PART" + +# Setup LVM +pvcreate "$LVM_PART" +vgcreate BL "$LVM_PART" + +# Create logical volumes +lvcreate -L 4G -n var02 BL +lvcreate -L 5G -n root02 BL + +# Create filesystems on logical volumes +mkfs.ext4 -F /dev/BL/var02 +mkfs.ext4 -F /dev/BL/root02 + +# Get UUIDs for bootc install +ROOT_UUID=$(blkid -s UUID -o value /dev/BL/root02) +BOOT_UUID=$(blkid -s UUID -o value "$EFI_PART") + +# Mount the partitions +mkdir -p /var/mnt/target +mount /dev/BL/root02 /var/mnt/target +mkdir -p /var/mnt/target/boot +mount "$BOOT_PART" /var/mnt/target/boot +mkdir -p /var/mnt/target/boot/efi +mount "$EFI_PART" /var/mnt/target/boot/efi + +# Create EFI directory structure with some files (simulating existing EFI content) +mkdir -p /var/mnt/target/boot/efi/EFI/fedora +touch /var/mnt/target/boot/efi/EFI/fedora/shimx64.efi +touch /var/mnt/target/boot/efi/EFI/fedora/grubx64.efi + +# Critical: Mount /var as a separate partition +mkdir -p /var/mnt/target/var +mount /dev/BL/var02 /var/mnt/target/var + +echo "Filesystem layout:" +mount | grep /var/mnt/target || true +df -h /var/mnt/target /var/mnt/target/boot /var/mnt/target/boot/efi /var/mnt/target/var + +# Run bootc install to-filesystem +# This should succeed and handle the separate /var mount correctly +podman run \ + --rm --privileged \ + -v /var/mnt/target:/target \ + -v /dev:/dev \ + --pid=host \ + --security-opt label=type:unconfined_t \ + "$TARGET_IMAGE" \ + bootc install to-filesystem \ + --disable-selinux \ + --karg=root=UUID="$ROOT_UUID" \ + --root-mount-spec=UUID="$ROOT_UUID" \ + --boot-mount-spec=UUID="$BOOT_UUID" \ + /target + +# Verify the installation succeeded +echo "Verifying installation..." +test -d /var/mnt/target/ostree +test -d /var/mnt/target/ostree/repo +# Verify bootloader was installed (grub2 or loader for different configurations) +test -d /var/mnt/target/boot/grub2 || test -d /var/mnt/target/boot/loader + +echo "Installation to-filesystem with separate /var mount succeeded!" diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf index eb715ce8..6344deb0 100644 --- a/tmt/tests/tests.fmf +++ b/tmt/tests/tests.fmf @@ -79,3 +79,13 @@ summary: Onboard to unified storage, build derived image, and switch to it duration: 30m test: nu booted/test-switch-to-unified.nu + +/test-32-install-to-filesystem-var-mount: + summary: Test bootc install to-filesystem with separate /var mount + duration: 30m + require: + - parted + - lvm2 + - dosfstools + - e2fsprogs + test: bash booted/test-install-to-filesystem-var-mount.sh