From 5bff67d485f22fcbd391231dad1666cc849deb36 Mon Sep 17 00:00:00 2001 From: Zach Reizner Date: Wed, 7 Aug 2019 14:49:34 -0700 Subject: tests: test to see if crosvm can boot a kernel This is the first `cargo test` which uses a linux kernel binary in testing crosvm's major functionality. To get that binary, the test tries various sources which may be influenced by the enviroment. If need be, the chromiumos kernel is downloaded via git, configured, and built so that crosvm can use it. The resulting binary is cached so that other tests which need it can run much quicker. TEST=cargo test -- boot BUG=None Cq-Depend: chromium:1867729 Change-Id: I123441d358ef886e0d7369eaa4ebd87373026d99 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1742924 Reviewed-by: Stephen Barber Tested-by: Zach Reizner Commit-Queue: Zach Reizner Auto-Submit: Zach Reizner --- tests/boot.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 tests/boot.rs (limited to 'tests') diff --git a/tests/boot.rs b/tests/boot.rs new file mode 100644 index 0000000..b4e38e1 --- /dev/null +++ b/tests/boot.rs @@ -0,0 +1,241 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::env; +use std::fs; +use std::io::{stdout, Write}; +use std::mem; +use std::os::unix::fs::symlink; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::Once; + +use libc::{cpu_set_t, sched_getaffinity}; + +use crosvm::{linux, Config, Executable}; +use devices::{SerialParameters, SerialType}; + +const CHROOT_KERNEL_PATH: &str = "/mnt/host/source/src/third_party/kernel/v4.19/"; +const CONTAINER_VM_DEFCONFIG: &str = "arch/x86/configs/chromiumos-container-vm-x86_64_defconfig"; +const KERNEL_REPO: &str = "https://chromium.googlesource.com/chromiumos/third_party/kernel"; +const KERNEL_REPO_BRANCH: &str = "chromeos-4.19"; +// TODO(zachr): this URL is a placeholder until the automated builder is running and we've settled +// on a location. +const KERNEL_PREBUILT: &str = "http://storage.googleapis.com/crosvm-testing"; + +/// Returns the number of CPUs that this process and its children can use by querying our process's +/// CPU affinity. +fn get_cpu_count() -> usize { + unsafe { + let mut set: cpu_set_t = mem::zeroed(); + let ret = sched_getaffinity(0, mem::size_of::(), &mut set); + if ret != 0 { + // A good guess. + 4 + } else { + // The cpu_set_t is normally counted using the CPU_COUNT macro, but we don't have that + // in Rust. Because it's always a bitset, we will treat it like one here. + let set: [u8; mem::size_of::()] = mem::transmute(set); + set.iter().map(|b| b.count_ones() as usize).sum() + } + } +} + +/// Clones a chrome os kernel into the given path. +fn clone_kernel_source(dir: &Path) { + let status = Command::new("git") + .args(&[ + "clone", + "--depth", + "1", + "--branch", + KERNEL_REPO_BRANCH, + KERNEL_REPO, + ]) + .arg(dir) + .status() + .expect("failed to execute git"); + if !status.success() { + panic!("failed to clone kernel source: {}", status); + } +} + +// Kernel binary algorithm. +// 1: If CROSVM_CARGO_TEST_KERNEL_BINARY is in the env: +// If CROSVM_CARGO_TEST_KERNEL_BINARY is empty, skip step 3. +// If CROSVM_CARGO_TEST_KERNEL_BINARY does not exist, panic. +// 2: If "bzImage" exists in the target directory use that. +// 3: Download "bzImage" from the KERNEL_PREBUILT url and use that. +// If the download does not work, go to the kernel source algorithm. +// +// Kernel source algorithm +// 1: If CROSVM_CARGO_TEST_KERNEL_SOURCE is in the env, use that. +// If CROSVM_CARGO_TEST_KERNEL_SOURCE does not exist, panic +// 2: If CHROOT_KERNEL_PATH exists, use that. +// 3: Checkout and use the chromeos kernel. +// +// Kernel config algorithm +// 1: If the .config already exists in the kernel source, use that. +// 2: If the CONTAINER_VM_DEFCONFIG exists in the kernel source, use that. +// 3: Use `make defconfig`. +fn prepare_kernel_once(dir: &Path) { + let kernel_binary = dir.join("bzImage"); + + let mut download_prebuilt = true; + if let Ok(env_kernel_binary) = env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") { + if env_kernel_binary.is_empty() { + download_prebuilt = false; + } else { + println!( + "using kernel binary from enviroment `{}`", + env_kernel_binary + ); + let env_kernel_binary = PathBuf::from(env_kernel_binary); + if env_kernel_binary.exists() { + symlink(env_kernel_binary, &kernel_binary) + .expect("failed to create symlink for kernel binary"); + return; + } else { + panic!( + "expected kernel binary at `{}`", + env_kernel_binary.display() + ) + } + } + } + + println!("looking for kernel binary at `{}`", kernel_binary.display()); + if kernel_binary.exists() { + println!("using kernel binary at `{}`", kernel_binary.display()); + return; + } + + if download_prebuilt { + // Resolve the base URL into a specific path for this architecture. + let kernel_prebuilt = format!( + "{}/{}/{}", + KERNEL_PREBUILT, + env::consts::ARCH, + "latest-bzImage" + ); + println!( + "downloading prebuilt kernel binary from `{}`", + kernel_prebuilt + ); + let status = Command::new("curl") + .args(&["--fail", "--location"]) + .arg("--output") + .arg(&kernel_binary) + .arg(kernel_prebuilt) + .status(); + if let Ok(status) = status { + if status.success() { + println!("using prebuilt kernel binary"); + return; + } + } + + println!("failed to download prebuilt kernel binary"); + } + + let kernel_source = if let Ok(env_kernel_source) = env::var("CROSVM_CARGO_TEST_KERNEL_SOURCE") { + if Path::new(&env_kernel_source).is_dir() { + PathBuf::from(env_kernel_source) + } else { + panic!("expected kernel source at `{}`", env_kernel_source); + } + } else if Path::new(CHROOT_KERNEL_PATH).is_dir() { + PathBuf::from(CHROOT_KERNEL_PATH) + } else { + let kernel_source = dir.join("kernel-source"); + // Check for kernel source + if !kernel_source.is_dir() { + clone_kernel_source(&kernel_source); + } + kernel_source + }; + + println!("building kernel from source `{}`", kernel_source.display()); + + // Special provisions for using the ChromeOS kernel source and its config used in crostini. + let current_config = kernel_source.join(".config"); + let container_vm_defconfig = kernel_source.join(CONTAINER_VM_DEFCONFIG); + if current_config.exists() { + fs::copy(current_config, dir.join(".config")) + .expect("failed to copy existing kernel config"); + } else if container_vm_defconfig.exists() { + fs::copy(container_vm_defconfig, dir.join(".config")) + .expect("failed to copy chromiumos container vm kernel config"); + } else { + // TODO(zachr): the defconfig for vanilla kernels is probably inadequate. There should + // probably be a step where additional options are added to the resulting .config. + let status = Command::new("make") + .current_dir(&kernel_source) + .arg(format!("O={}", dir.display())) + .arg("defconfig") + .status() + .expect("failed to execute make"); + if !status.success() { + panic!("failed to default config kernel: {}", status); + } + } + + let output = Command::new("make") + .current_dir(&kernel_source) + .arg(format!("O={}", dir.display())) + .args(&["bzImage", "-j"]) + .arg(format!("{}", get_cpu_count())) + .output() + .expect("failed to execute make"); + if !output.status.success() { + let _ = stdout().lock().write(&output.stderr); + panic!("failed to build kernel: {}", output.status); + } + + fs::copy(dir.join("arch/x86/boot/bzImage"), &kernel_binary) + .expect("failed to copy kernel binary"); +} + +/// Gets the target directory path for artifacts. +fn get_target_path() -> PathBuf { + env::current_exe() + .ok() + .map(|mut path| { + path.pop(); + path + }) + .expect("failed to get target dir") +} + +/// Thread-safe method for preparing a kernel and returning the path to its binary. +fn prepare_kernel() -> PathBuf { + // Lots of unit tests need the kernel, but it should only get prepared once by any arbitrary + // test. The rest of the tests should wait until the arbitrary one finishes. + let default_linux_dir = get_target_path(); + static PREP_ONCE: Once = Once::new(); + PREP_ONCE.call_once(|| prepare_kernel_once(&default_linux_dir)); + default_linux_dir.join("bzImage") +} + +#[test] +fn boot() { + let kernel_path = prepare_kernel(); + + let mut c = Config::default(); + c.sandbox = false; + c.serial_parameters.insert( + 1, + SerialParameters { + type_: SerialType::Sink, + path: None, + num: 1, + console: false, + stdin: false, + }, + ); + c.executable_path = Some(Executable::Kernel(kernel_path)); + + let r = linux::run_config(c); + r.expect("failed to run linux"); +} -- cgit 1.4.1