|
| 1 | +use anyhow::{anyhow, Context, Result}; |
| 2 | +use fn_error_context::context; |
| 3 | +use libtest_mimic::Trial; |
| 4 | +use rexpect::session::PtySession; |
| 5 | +use rustix::fs::statfs; |
| 6 | +use std::{ |
| 7 | + fs::{self}, |
| 8 | + path::Path, |
| 9 | +}; |
| 10 | + |
| 11 | +use crate::install; |
| 12 | + |
| 13 | +const TIMEOUT: u64 = 120000; |
| 14 | + |
| 15 | +fn get_deployment_dir() -> Result<std::path::PathBuf> { |
| 16 | + let base_path = Path::new("/ostree/deploy/default/deploy"); |
| 17 | + |
| 18 | + let entries: Vec<fs::DirEntry> = fs::read_dir(base_path) |
| 19 | + .with_context(|| format!("Failed to read directory: {}", base_path.display()))? |
| 20 | + .filter_map(|entry| match entry { |
| 21 | + Ok(e) if e.path().is_dir() => Some(e), |
| 22 | + _ => None, |
| 23 | + }) |
| 24 | + .collect::<Vec<_>>(); |
| 25 | + |
| 26 | + assert_eq!( |
| 27 | + entries.len(), |
| 28 | + 1, |
| 29 | + "Expected exactly one deployment directory" |
| 30 | + ); |
| 31 | + |
| 32 | + let deploy_dir_entry = &entries[0]; |
| 33 | + assert!( |
| 34 | + deploy_dir_entry.file_type()?.is_dir(), |
| 35 | + "deployment directory entry is not a directory: {}", |
| 36 | + base_path.display() |
| 37 | + ); |
| 38 | + |
| 39 | + let hash = deploy_dir_entry.file_name(); |
| 40 | + let hash_str = hash |
| 41 | + .to_str() |
| 42 | + .ok_or_else(|| anyhow!("Deployment directory name {:?} is not valid UTF-8", hash))?; |
| 43 | + |
| 44 | + println!("Using deployment directory: {}", hash_str); |
| 45 | + |
| 46 | + Ok(base_path.join(hash_str)) |
| 47 | +} |
| 48 | + |
| 49 | +#[context("System reinstall tests")] |
| 50 | +pub(crate) fn run(image: &str, testargs: libtest_mimic::Arguments) -> Result<()> { |
| 51 | + // Just leak the image name so we get a static reference as required by the test framework |
| 52 | + let image: &'static str = String::from(image).leak(); |
| 53 | + |
| 54 | + let tests = [ |
| 55 | + Trial::test("default behavior", move || { |
| 56 | + let sh = &xshell::Shell::new()?; |
| 57 | + install::reset_root(sh, image)?; |
| 58 | + |
| 59 | + let mut p: PtySession = rexpect::spawn( |
| 60 | + format!("/usr/bin/system-reinstall-bootc {image}").as_str(), |
| 61 | + Some(TIMEOUT), |
| 62 | + )?; |
| 63 | + |
| 64 | + // Basic flow stdout verification |
| 65 | + p.exp_regex("Found only one user ([^:]+) with ([\\d]+) SSH authorized keys.")?; |
| 66 | + p.exp_string("Would you like to import its SSH authorized keys")?; |
| 67 | + p.exp_string("into the root user on the new bootc system?")?; |
| 68 | + p.exp_string("Then you can login as root@ using those keys. [Y/n]")?; |
| 69 | + p.send_line("a")?; |
| 70 | + |
| 71 | + p.exp_string("Going to run command:")?; |
| 72 | + |
| 73 | + 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())?; |
| 74 | + p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?; |
| 75 | + |
| 76 | + p.send_line("y")?; |
| 77 | + |
| 78 | + p.exp_string(format!("Installing image: docker://{image}").as_str())?; |
| 79 | + p.exp_string("Initializing ostree layout")?; |
| 80 | + p.exp_string("Operation complete, rebooting in 10 seconds. Press Ctrl-C to cancel reboot, or press enter to continue immediately.")?; |
| 81 | + p.send_control('c')?; |
| 82 | + |
| 83 | + p.exp_eof()?; |
| 84 | + |
| 85 | + install::generic_post_install_verification()?; |
| 86 | + |
| 87 | + // Check for destructive cleanup and ssh key files |
| 88 | + let target_deployment_dir = |
| 89 | + get_deployment_dir().with_context(|| "Failed to get deployment directory")?; |
| 90 | + |
| 91 | + let files = [ |
| 92 | + "usr/lib/bootc/fedora-bootc-destructive-cleanup", |
| 93 | + "usr/lib/systemd/system/bootc-destructive-cleanup.service", |
| 94 | + "usr/lib/systemd/system/multi-user.target.wants/bootc-destructive-cleanup.service", |
| 95 | + "etc/tmpfiles.d/bootc-root-ssh.conf", |
| 96 | + ]; |
| 97 | + |
| 98 | + for f in files { |
| 99 | + let full_path = target_deployment_dir.join(f); |
| 100 | + assert!( |
| 101 | + full_path.exists(), |
| 102 | + "File not found: {}", |
| 103 | + full_path.display() |
| 104 | + ); |
| 105 | + } |
| 106 | + |
| 107 | + Ok(()) |
| 108 | + }), |
| 109 | + Trial::test("disk space check", move || { |
| 110 | + let sh = &xshell::Shell::new()?; |
| 111 | + install::reset_root(sh, image)?; |
| 112 | + |
| 113 | + // Allocate a file with the size of the available space on the root partition |
| 114 | + let stat = statfs("/")?; |
| 115 | + let available_space_bytes: u64 = stat.f_bsize as u64 * stat.f_bavail as u64; |
| 116 | + let file_size = available_space_bytes - (250 * 1024 * 1024); //leave 250 MiB free |
| 117 | + |
| 118 | + let tempfile = tempfile::Builder::new().tempfile_in("/")?; |
| 119 | + let tempfile_path = tempfile.path(); |
| 120 | + |
| 121 | + let file = std::fs::OpenOptions::new() |
| 122 | + .write(true) |
| 123 | + .create(true) |
| 124 | + .truncate(true) |
| 125 | + .open(tempfile_path)?; |
| 126 | + |
| 127 | + rustix::fs::fallocate(&file, rustix::fs::FallocateFlags::empty(), 0, file_size)?; |
| 128 | + |
| 129 | + // Run system-reinstall-bootc |
| 130 | + let mut p: PtySession = rexpect::spawn( |
| 131 | + format!("/usr/bin/system-reinstall-bootc {image}").as_str(), |
| 132 | + Some(TIMEOUT), |
| 133 | + )?; |
| 134 | + |
| 135 | + p.exp_regex("Found only one user ([^:]+) with ([\\d]+) SSH authorized keys.")?; |
| 136 | + p.send_line("a")?; |
| 137 | + p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?; |
| 138 | + p.send_line("y")?; |
| 139 | + p.exp_string("Insufficient free space")?; |
| 140 | + p.exp_eof()?; |
| 141 | + Ok(()) |
| 142 | + }), |
| 143 | + ]; |
| 144 | + |
| 145 | + libtest_mimic::run(&testargs, tests.into()).exit() |
| 146 | +} |
0 commit comments