Skip to content

Commit 3b277db

Browse files
committed
Initial set of system-reinstall-bootc integration tests
This adds a few basic integration tests for system-reinstall-bootc, adds a system-reinstall option to tests-integration to run them, and executes them as part of the github action. Signed-off-by: ckyrouac <[email protected]>
1 parent 3c86c7f commit 3b277db

File tree

7 files changed

+225
-5
lines changed

7 files changed

+225
-5
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ jobs:
108108
# Install tests
109109
sudo bootc-integration-tests install-alongside localhost/bootc
110110
111+
# system-reinstall-bootc tests
112+
cargo build --release -p system-reinstall-bootc
113+
sudo install -m 0755 target/release/system-reinstall-bootc /usr/bin/system-reinstall-bootc
114+
sudo bootc-integration-tests system-reinstall quay.io/centos-bootc/centos-bootc:stream9 --test-threads=1
115+
111116
# And the fsverity case
112117
sudo podman run --privileged --pid=host localhost/bootc-fsverity bootc install to-existing-root --stateroot=other \
113118
--acknowledge-destructive --skip-fetch-check

Cargo.lock

Lines changed: 45 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests-integration/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ fn-error-context = { workspace = true }
1919
indoc = { workspace = true }
2020
libtest-mimic = "0.8.0"
2121
oci-spec = "0.7.0"
22+
rexpect = "0.5"
2223
rustix = { workspace = true }
2324
serde = { workspace = true, features = ["derive"] }
2425
serde_json = { workspace = true }

tests-integration/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ full privileges, but is *not* destructive.
2222

2323
This suite is *DESTRUCTIVE*, executing the bootc `install to-existing-root`
2424
style flow using the host root. Run it in a transient virtual machine.
25+
26+
### `system-reinstall`
27+
28+
This suite is *DESTRUCTIVE*, executing the `system-reinstall-bootc`
29+
tests. Run it in a transient virtual machine.

tests-integration/src/install.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ const NON_DEFAULT_STATEROOT: &str = "foo";
1616

1717
/// Clear out and delete any ostree roots, leverage bootc hidden wipe-ostree command to get rid of
1818
/// otherwise hard to delete deployment files
19-
fn reset_root(sh: &Shell, image: &str) -> Result<()> {
19+
pub(crate) fn reset_root(sh: &Shell, image: &str) -> Result<()> {
2020
delete_ostree_deployments(sh, image)?;
2121
delete_ostree(sh)?;
2222
Ok(())
2323
}
2424

25-
fn delete_ostree(sh: &Shell) -> Result<(), anyhow::Error> {
25+
pub(crate) fn delete_ostree(sh: &Shell) -> Result<(), anyhow::Error> {
2626
if !Path::new("/ostree/").exists() {
2727
return Ok(());
2828
}
@@ -61,7 +61,7 @@ fn find_deployment_root() -> Result<Dir> {
6161
}
6262

6363
// Hook relatively cheap post-install tests here
64-
fn generic_post_install_verification() -> Result<()> {
64+
pub(crate) fn generic_post_install_verification() -> Result<()> {
6565
assert!(Utf8Path::new("/ostree/repo").try_exists()?);
6666
assert!(Utf8Path::new("/ostree/bootc/storage/overlay").try_exists()?);
6767
Ok(())
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use anyhow::{anyhow, Context, Result};
2+
use fn_error_context::context;
3+
use libtest_mimic::Trial;
4+
use rexpect::session::PtySession;
5+
use std::{
6+
fs::{self},
7+
path::Path,
8+
process::Command,
9+
};
10+
11+
use crate::install;
12+
13+
fn get_deployment_dir() -> Result<std::path::PathBuf> {
14+
let base_path = Path::new("/ostree/deploy/default/deploy");
15+
16+
let entries: Vec<fs::DirEntry> = fs::read_dir(base_path)
17+
.with_context(|| format!("Failed to read directory: {}", base_path.display()))?
18+
.filter_map(|entry| match entry {
19+
Ok(e) if e.path().is_dir() => Some(e),
20+
_ => None,
21+
})
22+
.collect::<Vec<_>>();
23+
24+
assert_eq!(
25+
entries.len(),
26+
1,
27+
"Expected exactly one deployment directory"
28+
);
29+
30+
let deploy_dir_entry = &entries[0];
31+
assert!(
32+
deploy_dir_entry.file_type()?.is_dir(),
33+
"deployment directory entry is not a directory: {}",
34+
base_path.display()
35+
);
36+
37+
let hash = deploy_dir_entry.file_name();
38+
let hash_str = hash
39+
.to_str()
40+
.ok_or_else(|| anyhow!("Deployment directory name {:?} is not valid UTF-8", hash))?;
41+
42+
println!("Using deployment directory: {}", hash_str);
43+
44+
Ok(base_path.join(hash_str))
45+
}
46+
47+
#[context("System reinstall tests")]
48+
pub(crate) fn run(image: &str, testargs: libtest_mimic::Arguments) -> Result<()> {
49+
// Just leak the image name so we get a static reference as required by the test framework
50+
let image: &'static str = String::from(image).leak();
51+
52+
let tests = [
53+
Trial::test("default behavior", move || {
54+
let sh = &xshell::Shell::new()?;
55+
install::reset_root(sh, image)?;
56+
57+
let mut p: PtySession = rexpect::spawn(
58+
format!("/usr/bin/system-reinstall-bootc {image}").as_str(),
59+
Some(600000),
60+
)?;
61+
62+
// Basic flow stdout verification
63+
p.exp_string("Select which user's SSH authorized keys you want to import into")?;
64+
p.exp_string("the root user of the new bootc system.")?;
65+
p.exp_string("Then you can login as root@ using those keys.")?;
66+
p.exp_string("(arrow keys to move, space to select):")?;
67+
p.send_line("a")?;
68+
69+
p.exp_string("Going to run command:")?;
70+
71+
p.exp_regex(format!("podman run --privileged --pid=host --user=root:root -v /var/lib/containers:/var/lib/containers -v /dev:/dev --security-opt label=type:unconfined_t -v /:/target -v /tmp/(.*):/bootc_authorized_ssh_keys/root {image} bootc install to-existing-root --acknowledge-destructive --skip-fetch-check --cleanup --root-ssh-authorized-keys /bootc_authorized_ssh_keys/root").as_str())?;
72+
p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?;
73+
74+
p.send_line("y")?;
75+
76+
p.exp_string(format!("Installing image: docker://{image}").as_str())?;
77+
p.exp_string("Initializing ostree layout")?;
78+
p.exp_string("Operation complete, rebooting in 10 seconds. Press Ctrl-C to cancel reboot, or press enter to continue immediately.")?;
79+
p.send_control('c')?;
80+
81+
p.exp_eof()?;
82+
83+
install::generic_post_install_verification()?;
84+
85+
// Check for destructive cleanup and ssh key files
86+
let target_deployment_dir =
87+
get_deployment_dir().with_context(|| "Failed to get deployment directory")?;
88+
89+
let files = [
90+
"usr/lib/bootc/fedora-bootc-destructive-cleanup",
91+
"usr/lib/systemd/system/bootc-destructive-cleanup.service",
92+
"usr/lib/systemd/system/multi-user.target.wants/bootc-destructive-cleanup.service",
93+
"etc/tmpfiles.d/bootc-root-ssh.conf",
94+
];
95+
96+
for f in files {
97+
let full_path = target_deployment_dir.join(f);
98+
assert!(
99+
full_path.exists(),
100+
"File not found: {}",
101+
full_path.display()
102+
);
103+
}
104+
105+
Ok(())
106+
}),
107+
Trial::test("disk space check", move || {
108+
let sh = &xshell::Shell::new()?;
109+
install::reset_root(sh, image)?;
110+
111+
// Allocate a file with the size of the available space on the root partition
112+
let output = Command::new("df")
113+
.arg("--block-size=1G")
114+
.arg("/")
115+
.output()
116+
.expect("Failed to execute df command");
117+
118+
let output_str = String::from_utf8_lossy(&output.stdout);
119+
let lines: Vec<&str> = output_str.lines().collect();
120+
let root_partition_info = lines.get(1).expect("Failed to parse df output");
121+
let available_space_gb: u64 = root_partition_info
122+
.split_whitespace()
123+
.nth(3)
124+
.expect("Failed to get available space")
125+
.parse()
126+
.expect("Failed to parse available space");
127+
128+
let file_size_gb = available_space_gb.saturating_sub(1);
129+
130+
let tempfile = tempfile::Builder::new().tempfile_in("/")?;
131+
let tempfile_path = tempfile.path();
132+
133+
let fallocate_status = Command::new("fallocate")
134+
.arg("-l")
135+
.arg(format!("{}GiB", file_size_gb))
136+
.arg(tempfile_path)
137+
.status()?;
138+
139+
assert!(fallocate_status.success(), "fallocate command failed");
140+
141+
// Run system-reinstall-bootc
142+
let mut p: PtySession = rexpect::spawn(
143+
format!("/usr/bin/system-reinstall-bootc {image}").as_str(),
144+
Some(600000),
145+
)?;
146+
147+
p.exp_string("Select which user's SSH authorized keys you want to import into")?;
148+
p.send_line("a")?;
149+
p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?;
150+
p.send_line("y")?;
151+
p.exp_string("Insufficient free space")?;
152+
p.exp_eof()?;
153+
Ok(())
154+
}),
155+
];
156+
157+
libtest_mimic::run(&testargs, tests.into()).exit()
158+
}

tests-integration/src/tests-integration.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@ mod hostpriv;
99
mod install;
1010
mod runvm;
1111
mod selinux;
12+
mod system_reinstall;
1213

1314
#[derive(Debug, Parser)]
1415
#[clap(name = "bootc-integration-tests", version, rename_all = "kebab-case")]
1516
pub(crate) enum Opt {
17+
SystemReinstall {
18+
/// Source container image reference
19+
image: String,
20+
#[clap(flatten)]
21+
testargs: libtest_mimic::Arguments,
22+
},
1623
InstallAlongside {
1724
/// Source container image reference
1825
image: String,
@@ -45,6 +52,7 @@ pub(crate) enum Opt {
4552
fn main() {
4653
let opt = Opt::parse();
4754
let r = match opt {
55+
Opt::SystemReinstall { image, testargs } => system_reinstall::run(&image, testargs),
4856
Opt::InstallAlongside { image, testargs } => install::run_alongside(&image, testargs),
4957
Opt::HostPrivileged { image, testargs } => hostpriv::run_hostpriv(&image, testargs),
5058
Opt::Container { testargs } => container::run(testargs),

0 commit comments

Comments
 (0)