lanzatool: intgeration test infrastrucutre + gc tests

This commit is contained in:
nikstur 2022-12-30 16:22:06 +01:00
parent 676786f811
commit d3a96b1c3c
5 changed files with 287 additions and 4 deletions

View File

@ -50,14 +50,14 @@
{ src
, target ? null
, doCheck ? true
, extraArgs ? { }
}:
let
cleanedSrc = craneLib.cleanCargoSource src;
commonArgs = {
src = cleanedSrc;
inherit src;
CARGO_BUILD_TARGET = target;
inherit doCheck;
};
} // extraArgs;
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
in
@ -73,7 +73,7 @@
};
lanzabooteCrane = buildRustApp {
src = ./rust/lanzaboote;
src = craneLib.cleanCargoSource ./rust/lanzaboote;
target = "x86_64-unknown-uefi";
doCheck = false;
};
@ -82,6 +82,13 @@
lanzatoolCrane = buildRustApp {
src = ./rust/lanzatool;
extraArgs = {
TEST_SYSTEMD = pkgs.systemd;
checkInputs = with pkgs; [
binutils-unwrapped
sbsigntool
];
};
};
lanzatool-unwrapped = lanzatoolCrane.package;
@ -134,6 +141,8 @@
lanzaboote
lanzatool
];
TEST_SYSTEMD = pkgs.systemd;
};
checks.x86_64-linux = {

View File

@ -0,0 +1,155 @@
use std::ffi::OsStr;
use std::fs;
use std::io::Write;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::Output;
use anyhow::{Context, Result};
use assert_cmd::Command;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use serde_json::json;
/// Create a mock generation link.
///
/// Creates the generation link using the specified version inside a mock profiles directory
/// (mimicking /nix/var/nix/profiles). Returns the path to the generation link.
pub fn setup_generation_link(
tmpdir: &Path,
profiles_directory: &Path,
version: u64,
) -> Result<PathBuf> {
let toplevel = setup_toplevel(tmpdir).context("Failed to setup toplevel")?;
let bootspec = json!({
"v1": {
"init": format!("init-v{}", version),
"initrd": toplevel.join("initrd"),
"kernel": toplevel.join("kernel"),
"kernelParams": [
"amd_iommu=on",
"amd_iommu=pt",
"iommu=pt",
"kvm.ignore_msrs=1",
"kvm.report_ignored_msrs=0",
"udev.log_priority=3",
"systemd.unified_cgroup_hierarchy=1",
"loglevel=4"
],
"label": "LanzaOS",
"toplevel": toplevel,
"system": "x86_64-linux",
"specialisation": {},
"extensions": {
"lanzaboote": { "osRelease": toplevel.join("os-release") }
}
}
});
let generation_link_path = profiles_directory.join(format!("system-{}-link", version));
fs::create_dir(&generation_link_path)?;
let bootspec_path = generation_link_path.join("boot.json");
let mut file = fs::File::create(bootspec_path)?;
file.write_all(&serde_json::to_vec(&bootspec)?)?;
Ok(generation_link_path)
}
/// Setup a mock toplevel inside a temporary directory.
///
/// Accepts the temporary directory as a parameter so that the invoking function retains control of
/// it (and when it goes out of scope).
fn setup_toplevel(tmpdir: &Path) -> Result<PathBuf> {
// Generate a random toplevel name so that multiple toplevel paths can live alongside each
// other in the same directory.
let toplevel = tmpdir.join(format!("toplevel-{}", random_string(8)));
fs::create_dir(&toplevel)?;
let test_systemd = systemd_location_from_env()?;
let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub");
let initrd_path = toplevel.join("initrd");
let kernel_path = toplevel.join("kernel");
let systemd_path = toplevel.join("systemd");
let os_release_path = toplevel.join("os-release");
// To simplify the test setup, we use the systemd stub for all PE binaries used by lanzatool.
// Lanzatool doesn't care whether its actually a kernel or initrd but only whether it can
// manipulate the PE binary with objcopy and/or sign it with sbsigntool. For testing lanzatool
// in isolation this should suffice.
fs::copy(&test_systemd_stub, initrd_path)?;
fs::copy(&test_systemd_stub, kernel_path)?;
symlink(&test_systemd, systemd_path)?;
setup_os_release(&os_release_path).context("Failed to setup os-release")?;
Ok(toplevel)
}
fn random_string(length: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
fn setup_os_release(path: &Path) -> Result<()> {
let content = r#"
ID=lanzaos
NAME=LanzaOS
PRETTY_NAME="LanzaOS 23.05 (Goat)"
VERSION="23.05 (Goat)"
"#;
let mut file = fs::File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
/// Call the `lanzaboote install` command.
pub fn lanzaboote_install(
config_limit: u64,
esp_mountpoint: &Path,
generation_links: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<Output> {
// To simplify the test setup, we use the systemd stub here instead of the lanzaboote stub. See
// the comment in setup_toplevel for details.
let test_systemd = systemd_location_from_env()?;
let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub");
let mut cmd = Command::cargo_bin("lanzatool")?;
let output = cmd
.env("LANZABOOTE_STUB", test_systemd_stub)
.arg("install")
.arg("--public-key")
.arg("tests/fixtures/uefi-keys/db.pem")
.arg("--private-key")
.arg("tests/fixtures/uefi-keys/db.key")
.arg("--configuration-limit")
.arg(config_limit.to_string())
.arg(esp_mountpoint)
.args(generation_links)
.output()?;
// Print debugging output.
// This is a weird hack to make cargo test capture the output.
// See https://github.com/rust-lang/rust/issues/12309
print!("{}", String::from_utf8(output.stdout.clone())?);
print!("{}", String::from_utf8(output.stderr.clone())?);
// Also walk the entire ESP mountpoint and print each path for debugging
for entry in walkdir::WalkDir::new(esp_mountpoint) {
println!("{}", entry?.path().display());
}
Ok(output)
}
/// Read location of systemd installation from an environment variable.
fn systemd_location_from_env() -> Result<String> {
let error_msg = "TEST_SYSTEMD environment variable is not set. TEST_SYSTEMD has to point to a systemd installation.
On a system with Nix installed, you can set it with: export TEST_SYSTEMD=$(nix-build '<nixpkgs>' -A systemd)";
std::env::var("TEST_SYSTEMD").context(error_msg)
}

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
3cEKjh9NywA7eLWDxRxEf/vq3vdWeFY=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE+TCCAuGgAwIBAgIRAPmrvtqrYB4tl9V5HFxxLYowDQYJKoZIhvcNAQELBQAw
LjEVMBMGA1UEBhMMRGF0YWJhc2UgS2V5MRUwEwYDVQQDEwxEYXRhYmFzZSBLZXkw
HhcNMjIxMTIzMTI1NjU1WhcNMjcxMTIzMTI1NjU1WjAuMRUwEwYDVQQGEwxEYXRh
YmFzZSBLZXkxFTATBgNVBAMTDERhdGFiYXNlIEtleTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBAK4tln0LCpqCkQeKqN1ddrxffWH+hNNwA3re40TRWTUp
3lowTMTOGukkfVpyqPpx8YyxEuxWL5r0xTxxfMpmGOz2tS5/KkzAKVIFnkf1LXWY
kMXS87ZYFP7qlt5T+Af9clzjgIVtjkhJAnF/gmJDzqkqsY+MWrRKympwSX8K957F
vkuNIreohHbWb6P4C/7sgtYBEWGALOO15nIoeGZT4cGnp40Mcz/6xaAnAfBMOG0q
A/bIxVllrkhIcc3BhShRhmVL8XZHXxcS11c4hUmzAtdNKrhl2l3A3AsdJbuVsN9q
AMMZ1+9BkQT/0q6z9coOib+8aA790WKFF+bbVDRgr6gaMK12MqGa3UkKFf1pCylH
xLNaPtxPFU11g1IK0z47OTxTJ93jH0x3g1uylc8zRs8Kbmry2PSwEQ3CEnoKDD4y
ito3ffhh3pxySUL7eHNFCPgVy6BUavHm9DKMfyB00iAGf5u9qSSGzfy4fcTXMrt2
sbW/3YfOXLKtf0OyUTx5TZZavcqGU10BLGK1pG3uvdn12tf8KGUXoxx8C/0NWtQS
KVkdItqjvKNPx1UgpLHZ2NttfGC0TP4vpphpj309m+A7/ItS8Uou5dnsnfcE6Kie
y2jCd+mZHEvpC13F5O6eIrOTKiXu4h6etyh6qYX+wqFG8ew8yvJ2oVhw4PkZw01z
AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAbh22
GBG/orDJrOClvt8qABL60ojnilLh4J5BwGDwBcXE/wETvXb+o3GsjTqqHdXYCQx0
e3SpR/xQCfKzuaE4ysaszKFcNbhaezLmo/pzD2v0UA6Jr56re/jkYMtLQL4+LAUP
kCH66vc6qETQwtAzV3CYQ5TirwLLD6z3luPhm2FzyXTU9eydbsRp7FuSKq8mVXKP
cskrVJSjWD7fOsy/sZq3S+xmA3w1C8XRAgz+xlfIQkuu0mAa3jJ3JGjDyu1lAZ6N
oUc897npQ2tpYyGRQ/zOmKSE9oUlcEqXLPk2nDgenhG0oAdM/jJ54rnuR2x3zhBx
gyPEvWVzjzX8E//9Kc6VocisnI/tckibUPHlMJFHFBXiU8m6vO28cCRl4tRsH/K9
jOnm+ztraiXI7mqBfTchR7Ga0EmnWFf38y3/YkrbVYbjK6o/+/T72Sje0MfCAvTl
KLOtvaQWQm36KP/2PH3DZ/k/v8+U76oeUBoI/DDH8E4rR1cUORvUYH8MZyPIKM4e
GN3+/zjr5gw70iLC/p8C+8rCzxwoernIMcFlL+SaeybnzXvH3VqO/XAFA/lxMu4V
K7Gzrtr5n+FiSTXytdIu18o6Av65XN5hzwQvVeWIx/E5Jry/JlSQgho8CnqLK8g5
USdRgvRJN7xJM3szP93f7aNgIYSys2cCxtZxunU=
-----END CERTIFICATE-----

View File

@ -0,0 +1,38 @@
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use tempfile::tempdir;
mod common;
#[test]
fn keep_only_configured_number_of_generations() -> Result<()> {
let esp_mountpoint = tempdir()?;
let tmpdir = tempdir()?;
let profiles = tempdir()?;
let generation_links: Vec<PathBuf> = [1, 2, 3]
.into_iter()
.map(|v| {
common::setup_generation_link(tmpdir.path(), profiles.path(), v)
.expect("Failed to setup generation link")
})
.collect();
let stub_count = || count_files(&esp_mountpoint.path().join("EFI/Linux")).unwrap();
// Install all 3 generations.
let output0 = common::lanzaboote_install(0, esp_mountpoint.path(), generation_links.clone())?;
assert!(output0.status.success());
assert_eq!(stub_count(), 3);
// Call `lanzatool install` again with a config limit of 2 and assert that one is deleted.
let output1 = common::lanzaboote_install(2, esp_mountpoint.path(), generation_links)?;
assert!(output1.status.success());
assert_eq!(stub_count(), 2);
Ok(())
}
fn count_files(path: &Path) -> Result<usize> {
Ok(fs::read_dir(path)?.count())
}