diff --git a/Dockerfile b/Dockerfile index 31f515800..9831efcca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -261,8 +261,9 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=bind,from=packaging,src=/,target=/run/packaging \ --mount=type=bind,from=packages,src=/,target=/run/packages \ /run/packaging/install-rpm-and-setup /run/packages -# Inject some other configuration -COPY --from=packaging /usr-extras/ /usr/ +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ + --mount=type=bind,from=packaging,src=/usr-extras,target=/run/usr-extras \ + install -D -m 0644 -t /usr/lib/bootc/kargs.d /run/usr-extras/lib/bootc/kargs.d/*.toml # Clean up package manager caches RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packaging,src=/,target=/run/packaging \ diff --git a/crates/xtask/src/tmt.rs b/crates/xtask/src/tmt.rs index 795830343..728eec97f 100644 --- a/crates/xtask/src/tmt.rs +++ b/crates/xtask/src/tmt.rs @@ -419,6 +419,25 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { println!("Found {} test plan(s): {:?}", plans.len(), plans); + // Determine base log directory: CLI flag > TMT_LOG_DIR env var > default. + // Filter out empty TMT_LOG_DIR (e.g. TMT_LOG_DIR="") to avoid creating + // log subdirectories in the current working directory. + let base_log_dir: Utf8PathBuf = if let Some(ref d) = args.log_dir { + d.clone() + } else if let Some(env_dir) = std::env::var("TMT_LOG_DIR").ok().filter(|s| !s.is_empty()) { + Utf8PathBuf::from(env_dir) + } else { + Utf8PathBuf::from("/var/tmp/tmt") + }; + + // Probe whether this bcvk supports --log-dir (added in bcvk 0.17). + // Older installs silently lack it; we skip the flag rather than hard-failing. + let bcvk_has_log_dir = cmd!(sh, "bcvk libvirt run --help") + .ignore_stderr() + .read() + .map(|help| help.contains("--log-dir")) + .unwrap_or(false); + // Generate a random suffix for VM names let random_suffix = generate_random_suffix(); @@ -483,11 +502,22 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { opts }; + // Set up per-VM log directory for journal + console capture (if bcvk supports it) + let vm_log_dir = base_log_dir.join(&vm_name); + let log_dir_args: Vec = if bcvk_has_log_dir { + std::fs::create_dir_all(&vm_log_dir) + .with_context(|| format!("Creating VM log directory {}", vm_log_dir))?; + println!("VM logs will be written to: {}", vm_log_dir); + vec![format!("--log-dir=journal,console={}", vm_log_dir)] + } else { + vec![] + }; + // Launch VM with bcvk let firmware_args_slice = firmware_args.as_slice(); let launch_result = cmd!( sh, - "bcvk libvirt run --name {vm_name} --detach {firmware_args_slice...} {COMMON_INST_ARGS...} {plan_bcvk_opts...} {image}" + "bcvk libvirt run --name {vm_name} --detach {firmware_args_slice...} {COMMON_INST_ARGS...} {plan_bcvk_opts...} {log_dir_args...} {image}" ) .run() .context("Launching VM with bcvk"); @@ -581,6 +611,12 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { println!("Verifying SSH connectivity..."); if let Err(e) = verify_ssh_connectivity(sh, ssh_port, &key_path) { eprintln!("SSH verification failed for plan {}: {:#}", plan, e); + if bcvk_has_log_dir { + eprintln!( + "VM logs (journal + console) may be available at: {}", + vm_log_dir + ); + } cleanup_vm(); all_passed = false; test_results.push((plan.to_string(), false, None)); diff --git a/crates/xtask/src/xtask.rs b/crates/xtask/src/xtask.rs index 79f4ffff6..be0a2c235 100644 --- a/crates/xtask/src/xtask.rs +++ b/crates/xtask/src/xtask.rs @@ -253,6 +253,12 @@ pub(crate) struct RunTmtArgs { /// Additional kernel arguments to pass to bcvk #[arg(long)] pub(crate) karg: Vec, + + /// Base directory for VM log files (journal + console). + /// Defaults to $TMT_LOG_DIR if set, otherwise /var/tmp/tmt. + /// Each VM gets its own subdirectory: `//` + #[arg(long)] + pub(crate) log_dir: Option, } impl RunTmtArgs { diff --git a/tmt/tests/booted/test-44-shadow-fixup.nu b/tmt/tests/booted/test-44-shadow-fixup.nu index fc73c438f..c4fa6fe8e 100644 --- a/tmt/tests/booted/test-44-shadow-fixup.nu +++ b/tmt/tests/booted/test-44-shadow-fixup.nu @@ -74,11 +74,12 @@ def second_boot [] { assert ($active_state == "active") $"bootc-sysusers-shadow-sync.service not active: ($active_state)" - # The service must have logged removing the stale gshadow entry. - let journal = (^journalctl -u bootc-sysusers-shadow-sync.service -b 0 --no-pager) - assert ($journal | str contains "orphaned") ( - $"bootc-sysusers-shadow-sync.service did not log removing orphaned entries;\njournal:\n($journal)" - ) + # Print the service journal for diagnostic context; don't assert on it. + # The log message is emitted via tracing_journald, which can be silently + # dropped if the journal socket is not yet visible at process start (e.g. + # volatile journals, early-boot races). The file-state checks below are + # the authoritative proof that the service did its job. + ^journalctl -u bootc-sysusers-shadow-sync.service -b 0 --no-pager | print # sysusers must have (re)created the group cleanly in /etc/group. let group_lines = (open /etc/group | lines | where { |l| $l | str starts-with "testbootcgroup:" })