Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests-integration: Add basic local tmt flow #593

Merged
merged 1 commit into from
Jun 18, 2024
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
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ bin-archive: all
test-bin-archive: all
$(MAKE) install-with-tests DESTDIR=tmp-install && $(TAR_REPRODUCIBLE) --zstd -C tmp-install -cf target/bootc.tar.zst . && rm tmp-install -rf

install-kola-tests:
install -D -t $(DESTDIR)$(prefix)/lib/coreos-assembler/tests/kola/bootc tests/kolainst/*
test-tmt:
cargo xtask test-tmt

validate:
cargo fmt
Expand Down
11 changes: 11 additions & 0 deletions plans/integration.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This tmt test just demonstrates local tmt usage.
# We'll hopefully expand it to do more interesting things in the
# future and unify with the other test plans.
provision:
how: virtual
# Generated by `cargo xtask `
image: file://./target/testbootc-cloud.qcow2
summary: Basic smoke test
execute:
how: tmt
script: bootc status
2 changes: 2 additions & 0 deletions tests-integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ camino = "1.1.6"
cap-std-ext = "4"
clap = { version= "4.5.4", features = ["derive","cargo"] }
fn-error-context = "0.2.1"
indoc = "2.0.5"
libtest-mimic = "0.7.3"
oci-spec = "0.6.5"
rustix = { "version" = "0.38.34", features = ["thread", "fs", "system", "process"] }
serde = { features = ["derive"], version = "1.0.199" }
serde_json = "1.0.116"
Expand Down
165 changes: 165 additions & 0 deletions tests-integration/src/runvm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use anyhow::{Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::Subcommand;
use fn_error_context::context;
use xshell::{cmd, Shell};

const BUILDER_ANNOTATION: &str = "bootc.diskimage-builder";
const TEST_IMAGE: &str = "localhost/bootc";
const TESTVMDIR: &str = "testvm";
const DISK_CACHE: &str = "disk.qcow2";
const IMAGEID_XATTR: &str = "user.bootc.container-image-digest";

#[derive(Debug, Subcommand)]
#[clap(rename_all = "kebab-case")]
pub(crate) enum Opt {
PrepareTmt {
#[clap(long)]
/// The container image to spawn, otherwise one will be built
testimage: Option<String>,
},
CreateQcow2 {
/// Input container image
container: String,
/// Write disk to this path
disk: Utf8PathBuf,
},
}

struct TestContext {
sh: xshell::Shell,
targetdir: Utf8PathBuf,
}

fn image_digest(sh: &Shell, cimage: &str) -> Result<String> {
let key = "{{ .Digest }}";
let r = cmd!(sh, "podman inspect --type image --format {key} {cimage}").read()?;
Ok(r)
}

fn builder_from_image(sh: &Shell, cimage: &str) -> Result<String> {
let mut inspect: serde_json::Value =
serde_json::from_str(&cmd!(sh, "podman inspect --type image {cimage}").read()?)?;
let inspect = inspect
.as_array_mut()
.and_then(|v| v.pop())
.ok_or_else(|| anyhow::anyhow!("Failed to parse inspect output"))?;
let config = inspect
.get("Config")
.ok_or_else(|| anyhow::anyhow!("Missing config"))?;
let config: oci_spec::image::Config =
serde_json::from_value(config.clone()).context("Parsing config")?;
let builder = config
.labels()
.as_ref()
.and_then(|l| l.get(BUILDER_ANNOTATION))
.ok_or_else(|| anyhow::anyhow!("Missing {BUILDER_ANNOTATION}"))?;
Ok(builder.to_owned())
}

#[context("Running bootc-image-builder")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use bootc install to-disk to generate disk image?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but I think down the line we'll migrate bib to be more of a SDK (cc https://gitlab.com/fedora/bootc/tracker/-/issues/2 ) and so we might as well start pulling a container here.

fn run_bib(sh: &Shell, cimage: &str, tmpdir: &Utf8Path, diskpath: &Utf8Path) -> Result<()> {
let diskpath: Utf8PathBuf = sh.current_dir().join(diskpath).try_into()?;
let digest = image_digest(sh, cimage)?;
println!("{cimage} digest={digest}");
if diskpath.try_exists()? {
let mut buf = [0u8; 2048];
if rustix::fs::getxattr(diskpath.as_std_path(), IMAGEID_XATTR, &mut buf)
.context("Reading xattr")
.is_ok()
{
let buf = String::from_utf8_lossy(&buf);
if &*buf == digest.as_str() {
println!("Existing disk {diskpath} matches container digest {digest}");
return Ok(());
} else {
println!("Cache miss; previous digest={buf}");
}
}
}
let builder = if let Ok(b) = std::env::var("BOOTC_BUILDER") {
b
} else {
builder_from_image(sh, cimage)?
};
let _g = sh.push_dir(tmpdir);
let bibwork = "bib-work";
sh.remove_path(bibwork)?;
sh.create_dir(bibwork)?;
let _g = sh.push_dir(bibwork);
let pwd = sh.current_dir();
cmd!(sh, "podman run --rm --privileged -v /var/lib/containers/storage:/var/lib/containers/storage --security-opt label=type:unconfined_t -v {pwd}:/output {builder} --type qcow2 --local {cimage}").run()?;
let tmp_disk: Utf8PathBuf = sh
.current_dir()
.join("qcow2/disk.qcow2")
.try_into()
.unwrap();
rustix::fs::setxattr(
tmp_disk.as_std_path(),
IMAGEID_XATTR,
digest.as_bytes(),
rustix::fs::XattrFlags::empty(),
)
.context("Setting xattr")?;
cmd!(sh, "mv -Tf {tmp_disk} {diskpath}").run()?;
cmd!(sh, "rm -rf {bibwork}").run()?;
Ok(())
}

/// Given the input container image reference, create a disk
/// image in the target directory.
#[context("Creating disk")]
fn create_disk(ctx: &TestContext, cimage: &str) -> Result<Utf8PathBuf> {
let sh = &ctx.sh;
let targetdir = ctx.targetdir.as_path();
let _targetdir_guard = sh.push_dir(targetdir);
sh.create_dir(TESTVMDIR)?;
let output_disk: Utf8PathBuf = sh
.current_dir()
.join(TESTVMDIR)
.join(DISK_CACHE)
.try_into()
.unwrap();

let bibwork = "bib-work";
sh.remove_path(bibwork)?;
sh.create_dir(bibwork)?;

run_bib(sh, cimage, bibwork.into(), &output_disk)?;

Ok(output_disk)
}

pub(crate) fn run(opt: Opt) -> Result<()> {
let ctx = &{
let sh = xshell::Shell::new()?;
let mut targetdir: Utf8PathBuf = cmd!(sh, "git rev-parse --show-toplevel").read()?.into();
targetdir.push("target");
TestContext { targetdir, sh }
};
match opt {
Opt::PrepareTmt { mut testimage } => {
let testimage = if let Some(i) = testimage.take() {
i
} else {
cmd!(
&ctx.sh,
"podman build --build-arg=variant=tmt -t {TEST_IMAGE} -f hack/Containerfile ."
)
.run()?;
TEST_IMAGE.to_string()
};

let disk = create_disk(ctx, &testimage)?;
println!("Created: {disk}");
Ok(())
}
Opt::CreateQcow2 { container, disk } => {
let g = ctx.sh.push_dir(&ctx.targetdir);
ctx.sh.remove_path("tmp")?;
ctx.sh.create_dir("tmp")?;
drop(g);
run_bib(&ctx.sh, &container, "tmp".into(), &disk)
}
}
}
4 changes: 4 additions & 0 deletions tests-integration/src/tests-integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use clap::Parser;
mod container;
mod hostpriv;
mod install;
mod runvm;
mod selinux;

#[derive(Debug, Parser)]
Expand All @@ -32,6 +33,8 @@ pub(crate) enum Opt {
#[clap(flatten)]
testargs: libtest_mimic::Arguments,
},
#[clap(subcommand)]
RunVM(runvm::Opt),
/// Extra helper utility to verify SELinux label presence
#[clap(name = "verify-selinux")]
VerifySELinux {
Expand All @@ -48,6 +51,7 @@ fn main() {
Opt::InstallAlongside { image, testargs } => install::run_alongside(&image, testargs),
Opt::HostPrivileged { image, testargs } => hostpriv::run_hostpriv(&image, testargs),
Opt::Container { testargs } => container::run(testargs),
Opt::RunVM(opts) => runvm::run(opts),
Opt::VerifySELinux { rootfs, warn } => {
let root = &Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority()).unwrap();
let mut path = PathBuf::from(".");
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Integration test includes two scenarios, `RPM build` and `bootc install/upgrade`
podman run --rm --privileged -v ./:/workdir:z -e TEST_OS=$TEST_OS -e ARCH=$ARCH -e RHEL_REGISTRY_URL=$RHEL_REGISTRY_URL -e DOWNLOAD_NODE=$DOWNLOAD_NODE --workdir /workdir quay.io/fedora/fedora:40 ./tests/integration/mockbuild.sh
```

#### Run Integartion Test
#### Run Integration Test

Run on a shared test infrastructure using the [`testing farm`](https://docs.testing-farm.io/Testing%20Farm/0.1/cli.html) tool. For example, running on AWS.

Expand Down
8 changes: 8 additions & 0 deletions xtask/src/xtask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const TASKS: &[(&str, fn(&Shell) -> Result<()>)] = &[
("package", package),
("package-srpm", package_srpm),
("custom-lints", custom_lints),
("test-tmt", test_tmt),
];

fn try_main() -> Result<()> {
Expand Down Expand Up @@ -142,6 +143,13 @@ fn man2markdown(sh: &Shell) -> Result<()> {
Ok(())
}

#[context("test-integration")]
fn test_tmt(sh: &Shell) -> Result<()> {
cmd!(sh, "cargo run -p tests-integration run-vm prepare-tmt").run()?;
cmd!(sh, "tmt run plans -n integration").run()?;
Ok(())
}

/// Return a string formatted version of the git commit timestamp, up to the minute
/// but not second because, well, we're not going to build more than once a second.
#[context("Finding git timestamp")]
Expand Down
Loading