diff --git a/Cargo.lock b/Cargo.lock index 985cf26812..8c00a5a50f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,12 +1054,14 @@ dependencies = [ "arch", "base", "cfg-if", + "crc32fast", "libc", "log", "prebuilts", "rand", "shlex", "tempfile", + "url", ] [[package]] @@ -1083,6 +1085,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fuse" version = "0.1.0" @@ -1320,6 +1331,16 @@ dependencies = [ "windows", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1904,6 +1925,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "perfetto" version = "0.1.0" @@ -2541,6 +2568,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.9" @@ -2579,18 +2621,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "usb_sys" version = "0.1.0" diff --git a/e2e_tests/fixture/Cargo.toml b/e2e_tests/fixture/Cargo.toml index 8787d1d0d1..04a64bb32d 100644 --- a/e2e_tests/fixture/Cargo.toml +++ b/e2e_tests/fixture/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" anyhow = "*" arch = { path = "../../arch" } base = "*" +crc32fast = "1.3" cfg-if = "*" libc = "0.2.65" rand = "0.8" @@ -15,6 +16,7 @@ tempfile = "3" prebuilts = { path = "../../prebuilts" } log = "*" shlex = "*" +url = "2.3" [features] direct = [] diff --git a/e2e_tests/fixture/src/sys/unix.rs b/e2e_tests/fixture/src/sys/unix.rs index 3564072b5c..cdec415fc5 100644 --- a/e2e_tests/fixture/src/sys/unix.rs +++ b/e2e_tests/fixture/src/sys/unix.rs @@ -26,8 +26,7 @@ use tempfile::TempDir; use crate::utils::find_crosvm_binary; use crate::utils::run_with_timeout; -use crate::vm::kernel_path; -use crate::vm::rootfs_path; +use crate::vm::local_path_from_url; use crate::vm::Config; const FROM_GUEST_PIPE: &str = "from_guest"; @@ -90,10 +89,12 @@ impl TestVmSys { // - ttyS1: Serial device attached to the named pipes. fn configure_serial_devices( command: &mut Command, + stdout_hardware_type: &str, from_guest_pipe: &Path, to_guest_pipe: &Path, ) { - command.args(["--serial", "type=stdout,hardware=virtio-console,console"]); + let stdout_serial_option = format!("type=stdout,hardware={},console", stdout_hardware_type); + command.args(["--serial", &stdout_serial_option]); // Setup channel for communication with the delegate. let serial_params = format!( @@ -105,10 +106,10 @@ impl TestVmSys { } /// Configures the VM rootfs to load from the guest_under_test assets. - fn configure_rootfs(command: &mut Command, o_direct: bool) { + fn configure_rootfs(command: &mut Command, o_direct: bool, path: &Path) { let rootfs_and_option = format!( "{}{},ro,root", - rootfs_path().to_str().unwrap(), + path.as_os_str().to_str().unwrap(), if o_direct { ",direct=true" } else { "" } ); command @@ -203,14 +204,24 @@ impl TestVmSys { pub fn append_config_args(command: &mut Command, test_dir: &Path, cfg: &Config) -> Result<()> { TestVmSys::configure_serial_devices( command, + &cfg.console_hardware, &test_dir.join(FROM_GUEST_PIPE), &test_dir.join(TO_GUEST_PIPE), ); command.args(["--socket", test_dir.join(CONTROL_PIPE).to_str().unwrap()]); - TestVmSys::configure_rootfs(command, cfg.o_direct); - // Set kernel as the last argument. - command.arg(kernel_path()); + if let Some(rootfs_url) = &cfg.rootfs_url { + TestVmSys::configure_rootfs(command, cfg.o_direct, &local_path_from_url(rootfs_url)); + }; + + // Set initrd if being requested + if let Some(initrd_url) = &cfg.initrd_url { + command.arg("--initrd"); + command.arg(local_path_from_url(initrd_url)); + } + + // Set kernel as the last argument. + command.arg(local_path_from_url(&cfg.kernel_url)); Ok(()) } @@ -219,42 +230,65 @@ impl TestVmSys { let config_file_path = test_dir.join(VM_JSON_CONFIG_FILE); let mut config_file = File::create(&config_file_path)?; + writeln!(config_file, "{{")?; + writeln!( + config_file, + r#""kernel": "{}""#, + local_path_from_url(&cfg.kernel_url).display() + )?; + if let Some(initrd_url) = &cfg.initrd_url { + writeln!( + config_file, + r#"",initrd": "{}""#, + local_path_from_url(initrd_url) + .to_str() + .context("invalid initrd path")? + )?; + }; writeln!( config_file, r#" - {{ - "kernel": "{}", - "socket": "{}", - "params": [ "init=/bin/delegate" ], - "serial": [ - {{ - "type": "stdout" - }}, - {{ - "type": "file", - "path": "{}", - "input": "{}", - "num": 2 - }} - ], - "block": [ - {{ - "path": "{}", - "ro": true, - "root": true, - "direct": {} - }} - ] - }} - "#, - kernel_path().display(), + ,"socket": "{}", + "params": [ "init=/bin/delegate" ], + "serial": [ + {{ + "type": "stdout" + }}, + {{ + "type": "file", + "path": "{}", + "input": "{}", + "num": 2 + }} + ] + "#, test_dir.join(CONTROL_PIPE).display(), test_dir.join(FROM_GUEST_PIPE).display(), test_dir.join(TO_GUEST_PIPE).display(), - rootfs_path().to_str().unwrap(), - cfg.o_direct, )?; + if let Some(rootfs_url) = &cfg.rootfs_url { + writeln!( + config_file, + r#" + ,"block": [ + {{ + "path": "{}", + "ro": true, + "root": true, + "direct": {} + }} + ] + "#, + local_path_from_url(rootfs_url) + .to_str() + .context("invalid rootfs path")?, + cfg.o_direct, + )?; + }; + + writeln!(config_file, "}}")?; + Ok(config_file_path) } diff --git a/e2e_tests/fixture/src/sys/windows.rs b/e2e_tests/fixture/src/sys/windows.rs index 0b6a075d50..02bd003e29 100644 --- a/e2e_tests/fixture/src/sys/windows.rs +++ b/e2e_tests/fixture/src/sys/windows.rs @@ -18,14 +18,14 @@ use std::sync::Arc; use std::sync::Mutex; use std::time::Duration; +use anyhow::Context; use anyhow::Result; use base::named_pipes; use base::PipeConnection; use rand::Rng; use crate::utils::find_crosvm_binary; -use crate::vm::kernel_path; -use crate::vm::rootfs_path; +use crate::vm::local_path_from_url; use crate::vm::Config; const GUEST_EARLYCON: &str = "guest_earlycon.log"; @@ -141,7 +141,12 @@ impl TestVmSys { // - ttyS0: Console device which prints kernel log / debug output of the // delegate binary. // - ttyS1: Serial device attached to the named pipes. - fn configure_serial_devices(command: &mut Command, from_guest_pipe: &Path, logs_dir: &Path) { + fn configure_serial_devices( + command: &mut Command, + stdout_hardware_type: &str, + from_guest_pipe: &Path, + logs_dir: &Path, + ) { let earlycon_path = Path::new(logs_dir).join(GUEST_EARLYCON); let earlycon_str = earlycon_path.to_str().unwrap(); @@ -154,7 +159,9 @@ impl TestVmSys { let console_str = console_path.to_str().unwrap(); command.args([ r"--serial", - &format!("hardware=virtio-console,num=1,type=file,path={console_str},console=true"), + &format!( + "hardware={stdout_hardware_type},num=1,type=file,path={console_str},console=true" + ), ]); // Setup channel for communication with the delegate. @@ -166,9 +173,11 @@ impl TestVmSys { } /// Configures the VM rootfs to load from the guest_under_test assets. - fn configure_rootfs(command: &mut Command, _o_direct: bool) { - let rootfs_and_option = - format!("{},ro,root,sparse=false", rootfs_path().to_str().unwrap(),); + fn configure_rootfs(command: &mut Command, _o_direct: bool, path: &Path) { + let rootfs_and_option = format!( + "{},ro,root,sparse=false", + path.as_os_str().to_str().unwrap(), + ); command.args(["--root", &rootfs_and_option]).args([ "--params", "init=/bin/delegate noxsaves noxsave nopat nopti tsc=reliable", @@ -232,12 +241,22 @@ impl TestVmSys { ) -> Result<()> { TestVmSys::configure_serial_devices( command, + &cfg.console_hardware, &serial_args.from_guest_pipe, &serial_args.logs_dir, ); - TestVmSys::configure_rootfs(command, cfg.o_direct); + if let Some(rootfs_url) = &cfg.rootfs_url { + TestVmSys::configure_rootfs(command, cfg.o_direct, &local_path_from_url(rootfs_url)); + }; + + // Set initrd if being requested + if let Some(initrd_url) = &cfg.initrd_url { + command.arg("--initrd"); + command.arg(local_path_from_url(initrd_url)); + } + // Set kernel as the last argument. - command.arg(kernel_path()); + command.arg(local_path_from_url(&cfg.kernel_url)); Ok(()) } @@ -246,15 +265,16 @@ impl TestVmSys { fn generate_json_config_file( from_guest_pipe: &Path, logs_path: &Path, - _cfg: &Config, + cfg: &Config, ) -> Result { let config_file_path = logs_path.join(VM_JSON_CONFIG_FILE); let mut config_file = File::create(&config_file_path)?; + writeln!(config_file, "{{")?; + writeln!( config_file, r#" - {{ "params": [ "init=/bin/delegate noxsaves noxsave nopat nopti tsc=reliable" ], "serial": [ {{ @@ -277,36 +297,62 @@ impl TestVmSys { "type": "namedpipe", "path": "{}", }}, - ], - "root": [ + ] + }} + "#, + logs_path.join(GUEST_EARLYCON).display(), + logs_path.join(GUEST_CONSOLE).display(), + from_guest_pipe.display() + )?; + + if let Some(rootfs_url) = &cfg.rootfs_url { + writeln!( + config_file, + r#" + ,"root": [ {{ "path": "{}", "ro": true, "root": true, "sparse": false }} - ], - "logs-directory": "{}", - "kernel-log-file": "{}, - "hypervisor": "{}" - {}, - {} - }} - "#, - logs_path.join(GUEST_EARLYCON).display(), - logs_path.join(GUEST_CONSOLE).display(), - from_guest_pipe.display(), - rootfs_path().to_str().unwrap(), + ] + "#, + local_path_from_url(rootfs_url) + .to_str() + .context("invalid rootfs path")?, + )?; + }; + if let Some(initrd_url) = &cfg.initrd_url { + writeln!( + config_file, + r#"",initrd": "{}""#, + local_path_from_url(initrd_url) + .to_str() + .context("invalid initrd path")? + )?; + }; + + writeln!( + config_file, + r#" + ,"logs-directory": "{}", + "kernel-log-file": "{}, + "hypervisor": "{}" + {}, + {}"#, logs_path.display(), logs_path.join(HYPERVISOR_LOG).display(), get_hypervisor(), - kernel_path().display(), + local_path_from_url(&cfg.kernel_url).display(), &get_irqchip(&get_hypervisor()).map_or("".to_owned(), |irqchip| format!( r#","irqchip": "{}""#, irqchip )) )?; + writeln!(config_file, "}}")?; + Ok(config_file_path) } diff --git a/e2e_tests/fixture/src/vm.rs b/e2e_tests/fixture/src/vm.rs index d77953f4ff..202fadf2b5 100644 --- a/e2e_tests/fixture/src/vm.rs +++ b/e2e_tests/fixture/src/vm.rs @@ -16,8 +16,10 @@ use anyhow::Context; use anyhow::Result; use base::syslog; use base::test_utils::check_can_sudo; +use crc32fast::hash; use log::Level; use prebuilts::download_file; +use url::Url; use crate::sys::SerialArgs; use crate::sys::TestVmSys; @@ -47,48 +49,38 @@ fn prebuilt_version() -> &'static str { include_str!("../../guest_under_test/PREBUILT_VERSION").trim() } -fn kernel_prebuilt_url() -> String { - format!( +fn kernel_prebuilt_url_string() -> Url { + Url::parse(&format!( "{}/guest-bzimage-{}-{}", PREBUILT_URL, ARCH, prebuilt_version() - ) + )) + .unwrap() } -fn rootfs_prebuilt_url() -> String { - format!( +fn rootfs_prebuilt_url_string() -> Url { + Url::parse(&format!( "{}/guest-rootfs-{}-{}", PREBUILT_URL, ARCH, prebuilt_version() - ) + )) + .unwrap() } -/// The kernel bzImage is stored next to the test executable, unless overridden by -/// CROSVM_CARGO_TEST_KERNEL_BINARY -pub(super) fn kernel_path() -> PathBuf { - match env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") { - Ok(value) => PathBuf::from(value), - Err(_) => env::current_exe() - .unwrap() - .parent() - .unwrap() - .join(format!("bzImage-{}", prebuilt_version())), +pub(super) fn local_path_from_url(url: &Url) -> PathBuf { + if url.scheme() == "file" { + return url.to_file_path().unwrap(); } -} - -/// The rootfs image is stored next to the test executable, unless overridden by -/// CROSVM_CARGO_TEST_ROOTFS_IMAGE -pub(super) fn rootfs_path() -> PathBuf { - match env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE") { - Ok(value) => PathBuf::from(value), - Err(_) => env::current_exe() - .unwrap() - .parent() - .unwrap() - .join(format!("rootfs-{}", prebuilt_version())), + if url.scheme() != "http" && url.scheme() != "https" { + panic!("Only file, http, https URLs are supported for artifacts") } + env::current_exe().unwrap().parent().unwrap().join(format!( + "e2e_prebuilt-{:x}-{:x}", + hash(url.as_str().as_bytes()), + hash(url.path().as_bytes()) + )) } /// Represents a command running in the guest. See `TestVm::exec_in_guest_async()` @@ -176,6 +168,18 @@ pub struct Config { /// Wrapper command line for executing `TestVM` pub(super) wrapper_cmd: Option, + + /// Url to kernel image + pub(super) kernel_url: Url, + + /// Url to initrd image + pub(super) initrd_url: Option, + + /// Url to rootfs image + pub(super) rootfs_url: Option, + + /// Console hardware type + pub(super) console_hardware: String, } impl Default for Config { @@ -186,6 +190,10 @@ impl Default for Config { o_direct: Default::default(), log_file: None, wrapper_cmd: None, + kernel_url: kernel_prebuilt_url_string(), + initrd_url: None, + rootfs_url: Some(rootfs_prebuilt_url_string()), + console_hardware: "virtio-console".to_owned(), } } } @@ -197,7 +205,6 @@ impl Config { } /// Uses extra arguments for `crosvm run`. - #[allow(dead_code)] pub fn extra_args(mut self, args: Vec) -> Self { let mut args = args; self.extra_args.append(&mut args); @@ -221,8 +228,36 @@ impl Config { env::var("CROSVM_CARGO_TEST_E2E_WRAPPER_CMD").map_or((), |x| cfg.wrapper_cmd = Some(x)); env::var("CROSVM_CARGO_TEST_LOG_FILE").map_or((), |x| cfg.log_file = Some(x)); env::var("CROSVM_CARGO_TEST_LOG_LEVEL_DEBUG").map_or((), |_| cfg.log_level = Level::Debug); + env::var("CROSVM_CARGO_TEST_KERNEL_IMAGE") + .map_or((), |x| cfg.kernel_url = Url::from_file_path(x).unwrap()); + env::var("CROSVM_CARGO_TEST_INITRD_IMAGE").map_or((), |x| { + cfg.initrd_url = Some(Url::from_file_path(x).unwrap()) + }); + env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE").map_or((), |x| { + cfg.rootfs_url = Some(Url::from_file_path(x).unwrap()) + }); cfg } + + pub fn with_kernel(mut self, url: &str) -> Self { + self.kernel_url = Url::parse(url).unwrap(); + self + } + + pub fn with_initrd(mut self, url: &str) -> Self { + self.initrd_url = Some(Url::parse(url).unwrap()); + self + } + + pub fn with_rootfs(mut self, url: &str) -> Self { + self.rootfs_url = Some(Url::parse(url).unwrap()); + self + } + + pub fn with_stdout_hardware(mut self, hw_type: &str) -> Self { + self.console_hardware = hw_type.to_owned(); + self + } } static PREP_ONCE: Once = Once::new(); @@ -264,24 +299,31 @@ impl TestVm { ); } } + } - let kernel_path = kernel_path(); - if env::var("CROSVM_CARGO_TEST_KERNEL_BINARY").is_err() { - if !kernel_path.exists() { - download_file(&kernel_prebuilt_url(), &kernel_path).unwrap(); - } + fn initiailize_artifacts(cfg: &Config) { + let kernel_path = local_path_from_url(&cfg.kernel_url); + if !kernel_path.exists() && cfg.kernel_url.scheme() != "file" { + download_file(cfg.kernel_url.as_str(), &kernel_path).unwrap(); } assert!(kernel_path.exists(), "{:?} does not exist", kernel_path); - let rootfs_path = rootfs_path(); - if env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE").is_err() { - if !rootfs_path.exists() { - download_file(&rootfs_prebuilt_url(), &rootfs_path).unwrap(); + if let Some(initrd_url) = &cfg.initrd_url { + let initrd_path = local_path_from_url(initrd_url); + if !initrd_path.exists() && initrd_url.scheme() != "file" { + download_file(initrd_url.as_str(), &initrd_path).unwrap(); } + assert!(initrd_path.exists(), "{:?} does not exist", initrd_path); } - assert!(rootfs_path.exists(), "{:?} does not exist", rootfs_path); - TestVmSys::check_rootfs_file(&rootfs_path); + if let Some(rootfs_url) = &cfg.rootfs_url { + let rootfs_path = local_path_from_url(rootfs_url); + if !rootfs_path.exists() && rootfs_url.scheme() != "file" { + download_file(rootfs_url.as_str(), &rootfs_path).unwrap(); + } + assert!(rootfs_path.exists(), "{:?} does not exist", rootfs_path); + TestVmSys::check_rootfs_file(&rootfs_path); + } } /// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt @@ -294,6 +336,9 @@ impl TestVm { F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>, { PREP_ONCE.call_once(TestVm::initialize_once); + + TestVm::initiailize_artifacts(&cfg); + let mut vm = TestVm { sys: TestVmSys::new_generic(f, cfg, sudo).with_context(|| "Could not start crosvm")?, ready: false, diff --git a/tools/bench b/tools/bench index 6238bb751b..0c9840b9fe 100755 --- a/tools/bench +++ b/tools/bench @@ -65,7 +65,12 @@ def parse_seccomp_trace_events_to_pid_table( bench = cmd("cargo test").with_color_flag() -def main(target_name: str, log_seccomp: bool = False, log_seccomp_output_dir: str = ""): +def main( + target_name: str, + log_seccomp: bool = False, + log_seccomp_output_dir: str = "", + nocapture: bool = False, +): """Run an end-to-end benchmark target. target-name -- name of target @@ -98,8 +103,12 @@ def main(target_name: str, log_seccomp: bool = False, log_seccomp_output_dir: st ), } ) - - bench("--release", "--bench", target_name).with_envs(build_env).fg() + if nocapture: + bench("--release", "--bench", target_name, "--", "--nocapture").with_envs( + build_env + ).fg() + else: + bench("--release", "--bench", target_name).with_envs(build_env).fg() if log_seccomp: with open(pathlib.Path(tempdir) / "crosvm.log", "r") as f: