Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ store-success-output = true
store-failure-output = true

# Per-test overrides for libvirt tests that create VMs
# These tests race on creating libvirt base disks, so they need to run serially
# VMs get 4 vcpus by default, so require 4 threads to prevent any parallelism
# Tests that run `bootc install` can be heavy, so require 4 threads to limit parallelism
[[profile.integration.overrides]]
filter = 'test(~^test_libvirt_run)'
filter = 'test(~^libvirt_run) | test(~^to_disk)'
threads-required = 4
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PRIMARY_IMAGE := "quay.io/centos-bootc/centos-bootc:stream10"
ALL_BASE_IMAGES := "quay.io/fedora/fedora-bootc:42 quay.io/centos-bootc/centos-bootc:stream9 quay.io/centos-bootc/centos-bootc:stream10"
ALL_BASE_IMAGES := "quay.io/fedora/fedora-bootc:42 quay.io/centos-bootc/centos-bootc:stream9 quay.io/centos-bootc/centos-bootc:stream10 quay.io/almalinuxorg/almalinux-bootc:9.6"

# Build the native binary
build:
Expand Down
154 changes: 105 additions & 49 deletions crates/integration-tests/src/tests/to_disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,76 @@

use camino::Utf8PathBuf;
use color_eyre::Result;
use integration_tests::integration_test;
use integration_tests::{integration_test, parameterized_integration_test};

use std::process::Command;
use tempfile::TempDir;

use crate::{get_test_image, run_bcvk, INTEGRATION_TEST_LABEL};
use crate::{get_test_image, run_bcvk, CapturedOutput, INTEGRATION_TEST_LABEL};

/// Validate that a disk image was created successfully with proper bootc installation
///
/// This helper function verifies:
/// - The disk image file exists and has non-zero size
/// - The disk has valid partition table (using sfdisk, only for raw images)
/// - The installation completed successfully (from output messages)
///
/// Note: sfdisk can only read partition tables from raw disk images, not qcow2.
/// For qcow2 images, partition validation is skipped.
fn validate_disk_image(
disk_path: &Utf8PathBuf,
output: &CapturedOutput,
context: &str,
) -> Result<()> {
let metadata = std::fs::metadata(disk_path).expect("Failed to get disk metadata");
Comment thread
cgwalters marked this conversation as resolved.
assert!(metadata.len() > 0, "{}: Disk image is empty", context);

// Only verify partitions for raw images - sfdisk can't read qcow2 format
let is_qcow2 = disk_path.as_str().ends_with(".qcow2");
if !is_qcow2 {
// Verify the disk has partitions using sfdisk -l
let sfdisk_output = Command::new("sfdisk")
.arg("-l")
.arg(disk_path.as_str())
.output()
.expect("Failed to run sfdisk");

let sfdisk_stdout = String::from_utf8_lossy(&sfdisk_output.stdout);

assert!(
sfdisk_output.status.success(),
"{}: sfdisk failed with exit code: {:?}",
context,
sfdisk_output.status.code()
);

assert!(
sfdisk_stdout.contains("Disk ")
&& (sfdisk_stdout.contains("sectors") || sfdisk_stdout.contains("bytes")),
"{}: sfdisk output doesn't show expected disk information",
context
);

let has_partitions = sfdisk_stdout.lines().any(|line| {
line.contains(disk_path.as_str()) && (line.contains("Linux") || line.contains("EFI"))
});

assert!(
has_partitions,
"{}: No bootc partitions found in sfdisk output. Output was:\n{}",
context, sfdisk_stdout
);
}

assert!(
output.stdout.contains("Installation complete") || output.stderr.contains("Installation complete"),
"{}: No 'Installation complete' message found in output. This indicates bootc install did not complete successfully. stdout: {}, stderr: {}",
context,
output.stdout, output.stderr
);

Ok(())
}

/// Test actual bootc installation to a disk image
fn test_to_disk() -> Result<()> {
Expand All @@ -45,45 +109,7 @@ fn test_to_disk() -> Result<()> {
output.stderr
);

let metadata = std::fs::metadata(&disk_path).expect("Failed to get disk metadata");
assert!(metadata.len() > 0);

// Verify the disk has partitions using sfdisk -l
let sfdisk_output = Command::new("sfdisk")
.arg("-l")
.arg(disk_path.as_str())
.output()
.expect("Failed to run sfdisk");

let sfdisk_stdout = String::from_utf8_lossy(&sfdisk_output.stdout);

assert!(
sfdisk_output.status.success(),
"sfdisk failed with exit code: {:?}",
sfdisk_output.status.code()
);

assert!(
sfdisk_stdout.contains("Disk ")
&& (sfdisk_stdout.contains("sectors") || sfdisk_stdout.contains("bytes")),
"sfdisk output doesn't show expected disk information"
);

let has_partitions = sfdisk_stdout.lines().any(|line| {
line.contains(disk_path.as_str()) && (line.contains("Linux") || line.contains("EFI"))
});

assert!(
has_partitions,
"No bootc partitions found in sfdisk output. Output was:\n{}",
sfdisk_stdout
);

assert!(
output.stdout.contains("Installation complete") || output.stderr.contains("Installation complete"),
"No 'Installation complete' message found in output. This indicates bootc install did not complete successfully. stdout: {}, stderr: {}",
output.stdout, output.stderr
);
validate_disk_image(&disk_path, &output, "test_to_disk")?;
Ok(())
}
integration_test!(test_to_disk);
Expand Down Expand Up @@ -111,9 +137,6 @@ fn test_to_disk_qcow2() -> Result<()> {
output.stderr
);

let metadata = std::fs::metadata(&disk_path).expect("Failed to get disk metadata");
assert!(metadata.len() > 0);

// Verify the file is actually qcow2 format using qemu-img info
let qemu_img_output = Command::new("qemu-img")
.args(["info", disk_path.as_str()])
Expand All @@ -134,11 +157,7 @@ fn test_to_disk_qcow2() -> Result<()> {
qemu_img_stdout
);

assert!(
output.stdout.contains("Installation complete") || output.stderr.contains("Installation complete"),
"No 'Installation complete' message found in output. This indicates bootc install did not complete successfully. stdout: {}, stderr: {}",
output.stdout, output.stderr
);
validate_disk_image(&disk_path, &output, "test_to_disk_qcow2")?;
Ok(())
}
integration_test!(test_to_disk_qcow2);
Expand Down Expand Up @@ -300,3 +319,40 @@ fn test_to_disk_different_imgref_same_digest() -> Result<()> {
Ok(())
}
integration_test!(test_to_disk_different_imgref_same_digest);

/// Test to-disk with various bootc images to ensure compatibility
///
/// This parameterized test runs to-disk with multiple container images,
/// particularly testing AlmaLinux which had cross-device link issues (issue #125)
fn test_to_disk_for_image(image: &str) -> Result<()> {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let disk_path = Utf8PathBuf::try_from(temp_dir.path().join("test-disk.img"))
.expect("temp path is not UTF-8");

let output = run_bcvk(&[
"to-disk",
"--label",
INTEGRATION_TEST_LABEL,
// Not all iamges have one
"--filesystem=ext4",
image,
disk_path.as_str(),
])?;

assert!(
output.success(),
"to-disk with image {} failed with exit code: {:?}. stdout: {}, stderr: {}",
image,
output.exit_code(),
output.stdout,
output.stderr
);

validate_disk_image(
&disk_path,
&output,
&format!("test_to_disk_multi_image({})", image),
)?;
Ok(())
}
parameterized_integration_test!(test_to_disk_for_image);
3 changes: 2 additions & 1 deletion crates/kit/src/to_disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,10 @@ impl ToDiskOpts {

# Execute bootc installation, having the outer podman pull from
# the virtiofs store on the host, as well as the inner bootc.
# Mount /var/tmp into inner container to avoid cross-device link errors (issue #125)
export STORAGE_OPTS=additionalimagestore=${AIS}
podman run --rm -i ${tty} --privileged --pid=host --net=none -v /sys:/sys:ro \
-v /var/lib/containers:/var/lib/containers -v /dev:/dev -v ${AIS}:${AIS} --security-opt label=type:unconfined_t \
-v /var/lib/containers:/var/lib/containers -v /var/tmp:/var/tmp -v /dev:/dev -v ${AIS}:${AIS} --security-opt label=type:unconfined_t \
--env=STORAGE_OPTS \
{INSTALL_LOG} \
{SOURCE_IMGREF} \
Expand Down
Loading