From 08e25857e1f29d86bc40143b7ebfd4ff9b711904 Mon Sep 17 00:00:00 2001 From: Jorge Prendes Date: Fri, 25 Aug 2023 22:41:44 +0100 Subject: [PATCH] merge run_wasi_test implementations Signed-off-by: Jorge Prendes --- .../src/libcontainer_instance/instance.rs | 2 +- .../src/sandbox/instance.rs | 10 +- .../src/sandbox/testutil.rs | 103 +++++++++++++++-- .../containerd-shim-wasm/src/sys/unix/mod.rs | 1 + .../src/sys/unix/signals.rs | 1 + .../src/sys/windows/mod.rs | 1 + .../src/sys/windows/signals.rs | 2 + .../src/instance/instance_linux.rs | 96 ++-------------- .../src/instance/instance_linux.rs | 107 ++---------------- 9 files changed, 122 insertions(+), 201 deletions(-) create mode 100644 crates/containerd-shim-wasm/src/sys/unix/signals.rs create mode 100644 crates/containerd-shim-wasm/src/sys/windows/signals.rs diff --git a/crates/containerd-shim-wasm/src/libcontainer_instance/instance.rs b/crates/containerd-shim-wasm/src/libcontainer_instance/instance.rs index f27895e6e..2510d9421 100644 --- a/crates/containerd-shim-wasm/src/libcontainer_instance/instance.rs +++ b/crates/containerd-shim-wasm/src/libcontainer_instance/instance.rs @@ -5,7 +5,6 @@ use std::thread; use anyhow::Context; use chrono::Utc; -use libc::{SIGINT, SIGKILL}; use libcontainer::container::{Container, ContainerStatus}; use libcontainer::signal::Signal; use log::error; @@ -16,6 +15,7 @@ use crate::sandbox::error::Error; use crate::sandbox::instance::{ExitCode, Wait}; use crate::sandbox::instance_utils::{get_instance_root, instance_exists}; use crate::sandbox::{Instance, InstanceConfig}; +use crate::sys::signals::{SIGINT, SIGKILL}; /// LibcontainerInstance is a trait that gets implemented by a WASI runtime that /// uses youki's libcontainer library as the container runtime. diff --git a/crates/containerd-shim-wasm/src/sandbox/instance.rs b/crates/containerd-shim-wasm/src/sandbox/instance.rs index e25de653b..32cfbc4e7 100644 --- a/crates/containerd-shim-wasm/src/sandbox/instance.rs +++ b/crates/containerd-shim-wasm/src/sandbox/instance.rs @@ -5,17 +5,12 @@ use std::sync::{Arc, Condvar, Mutex}; use std::thread; use chrono::{DateTime, Utc}; -use libc::{SIGINT, SIGTERM}; use super::error::Error; +use crate::sys::signals::*; pub type ExitCode = Arc<(Mutex)>>, Condvar)>; -#[cfg(unix)] -use libc::SIGKILL; -#[cfg(windows)] -const SIGKILL: i32 = 9; - /// Generic options builder for creating a wasm instance. /// This is passed to the `Instance::new` method. #[derive(Clone)] @@ -221,9 +216,6 @@ mod noptests { use std::sync::mpsc::channel; use std::time::Duration; - #[cfg(unix)] - use libc::SIGHUP; - use super::*; #[test] diff --git a/crates/containerd-shim-wasm/src/sandbox/testutil.rs b/crates/containerd-shim-wasm/src/sandbox/testutil.rs index d58228f3c..1fcc64d9e 100644 --- a/crates/containerd-shim-wasm/src/sandbox/testutil.rs +++ b/crates/containerd-shim-wasm/src/sandbox/testutil.rs @@ -1,8 +1,15 @@ //! Testing utilities used across different modules +use std::collections::HashMap; +use std::fs::{create_dir, File}; +use std::path::Path; use std::process::{Command, Stdio}; +use std::sync::mpsc::channel; +use std::time::Duration; -use super::{Error, Result}; +use anyhow::{bail, ensure, Result}; + +use crate::sys::signals::SIGKILL; fn normalize_test_name(test: &str) -> Result<&str> { let closure_removed = test.trim_end_matches("::{{closure}}"); @@ -50,15 +57,9 @@ pub fn run_test_with_sudo(test: &str) -> Result<()> { std::io::copy(&mut stderr, &mut std::io::stderr()).unwrap(); }); - cmd.wait() - .and_then(|status| { - if status.success() { - Ok(()) - } else { - Err(std::io::ErrorKind::Other.into()) - } - }) - .map_err(Error::from) + ensure!(cmd.wait()?.success(), "running test with sudo failed"); + + Ok(()) } #[macro_export] @@ -76,7 +77,12 @@ macro_rules! function { #[cfg(unix)] use caps::{CapSet, Capability}; +use chrono::{DateTime, Utc}; pub use function; +use oci_spec::runtime::{ProcessBuilder, RootBuilder, SpecBuilder}; + +use super::instance::Wait; +use super::{Instance, InstanceConfig}; /// Determines if the current process has the CAP_SYS_ADMIN capability in its effective set. pub fn has_cap_sys_admin() -> bool { @@ -91,3 +97,80 @@ pub fn has_cap_sys_admin() -> bool { false } } + +pub fn run_wasi_test( + dir: impl AsRef, + wasmbytes: impl AsRef<[u8]>, + start_fn: Option<&str>, +) -> Result<(u32, DateTime)> +where + WasmInstance::Engine: Default, +{ + create_dir(dir.as_ref().join("rootfs"))?; + let rootdir = dir.as_ref().join("runwasi"); + create_dir(&rootdir)?; + let opts = HashMap::from([("root", rootdir)]); + let opts_file = std::fs::File::create(dir.as_ref().join("options.json"))?; + serde_json::to_writer(opts_file, &opts)?; + + let filename = if wasmbytes.as_ref().starts_with(b"\0asm") { + "hello.wasm" + } else { + "hello.wat" + }; + + let wasm_path = dir.as_ref().join("rootfs").join(filename); + std::fs::write(&wasm_path, wasmbytes)?; + + #[cfg(unix)] + std::fs::set_permissions( + &wasm_path, + std::os::unix::prelude::PermissionsExt::from_mode(0o755), + )?; + + let stdout = File::create(dir.as_ref().join("stdout"))?; + drop(stdout); + + let entrypoint = match start_fn { + Some(s) => format!("./{filename}#{s}"), + None => format!("./{filename}"), + }; + let spec = SpecBuilder::default() + .root(RootBuilder::default().path("rootfs").build()?) + .process( + ProcessBuilder::default() + .cwd("/") + .args(vec![entrypoint]) + .build()?, + ) + .build()?; + + spec.save(dir.as_ref().join("config.json"))?; + + let mut cfg = InstanceConfig::new( + WasmInstance::Engine::default(), + "test_namespace".into(), + "/containerd/address".into(), + ); + let cfg = cfg + .set_bundle(dir.as_ref().to_str().unwrap().to_string()) + .set_stdout(dir.as_ref().join("stdout").to_str().unwrap().to_string()); + + let wasi = WasmInstance::new("test".to_string(), Some(cfg)); + + wasi.start()?; + + let (tx, rx) = channel(); + let waiter = Wait::new(tx); + wasi.wait(&waiter).unwrap(); + + let res = match rx.recv_timeout(Duration::from_secs(10)) { + Ok(res) => Ok(res), + Err(e) => { + wasi.kill(SIGKILL as u32).unwrap(); + bail!("error waiting for module to finish: {e}"); + } + }; + wasi.delete()?; + res +} diff --git a/crates/containerd-shim-wasm/src/sys/unix/mod.rs b/crates/containerd-shim-wasm/src/sys/unix/mod.rs index 968fb40cd..826127a3d 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/mod.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/mod.rs @@ -1,3 +1,4 @@ pub mod metrics; pub mod networking; +pub mod signals; pub mod stdio; diff --git a/crates/containerd-shim-wasm/src/sys/unix/signals.rs b/crates/containerd-shim-wasm/src/sys/unix/signals.rs new file mode 100644 index 000000000..af4f5dfa0 --- /dev/null +++ b/crates/containerd-shim-wasm/src/sys/unix/signals.rs @@ -0,0 +1 @@ +pub use libc::{SIGHUP, SIGINT, SIGKILL, SIGTERM}; diff --git a/crates/containerd-shim-wasm/src/sys/windows/mod.rs b/crates/containerd-shim-wasm/src/sys/windows/mod.rs index 968fb40cd..826127a3d 100644 --- a/crates/containerd-shim-wasm/src/sys/windows/mod.rs +++ b/crates/containerd-shim-wasm/src/sys/windows/mod.rs @@ -1,3 +1,4 @@ pub mod metrics; pub mod networking; +pub mod signals; pub mod stdio; diff --git a/crates/containerd-shim-wasm/src/sys/windows/signals.rs b/crates/containerd-shim-wasm/src/sys/windows/signals.rs new file mode 100644 index 000000000..18d3de55d --- /dev/null +++ b/crates/containerd-shim-wasm/src/sys/windows/signals.rs @@ -0,0 +1,2 @@ +pub use libc::{SIGINT, SIGTERM}; +pub const SIGKILL: i32 = 9; diff --git a/crates/containerd-shim-wasmedge/src/instance/instance_linux.rs b/crates/containerd-shim-wasmedge/src/instance/instance_linux.rs index 5e3f23401..af7a726aa 100644 --- a/crates/containerd-shim-wasmedge/src/instance/instance_linux.rs +++ b/crates/containerd-shim-wasmedge/src/instance/instance_linux.rs @@ -84,24 +84,18 @@ impl LibcontainerInstance for Wasi { #[cfg(test)] mod wasitest { - use std::borrow::Cow; - use std::collections::HashMap; - use std::fs::{create_dir, read_to_string, File, OpenOptions}; - use std::io::Write; + + use std::fs::read_to_string; use std::os::unix::io::RawFd; - use std::os::unix::prelude::OpenOptionsExt; - use std::sync::mpsc::channel; - use std::time::Duration; - use chrono::{DateTime, Utc}; use containerd_shim_wasm::function; - use containerd_shim_wasm::sandbox::instance::Wait; - use containerd_shim_wasm::sandbox::testutil::{has_cap_sys_admin, run_test_with_sudo}; + use containerd_shim_wasm::sandbox::testutil::{ + has_cap_sys_admin, run_test_with_sudo, run_wasi_test, + }; use containerd_shim_wasm::sandbox::Instance; - use libc::{dup2, SIGKILL, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; - use oci_spec::runtime::{ProcessBuilder, RootBuilder, SpecBuilder}; + use libc::{dup2, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use serial_test::serial; - use tempfile::{tempdir, TempDir}; + use tempfile::tempdir; use wasmedge_sdk::wat2wasm; use super::*; @@ -162,74 +156,6 @@ mod wasitest { "# .as_bytes(); - fn run_wasi_test(dir: &TempDir, wasmbytes: Cow<[u8]>) -> Result<(u32, DateTime), Error> { - create_dir(dir.path().join("rootfs"))?; - let rootdir = dir.path().join("runwasi"); - create_dir(rootdir)?; - let rootdir = PathBuf::from("/path/to/root"); - let mut opts = HashMap::new(); - opts.insert("root", rootdir); - let serialized = serde_json::to_string(&opts)?; - let opts_file = OpenOptions::new() - .read(true) - .create(true) - .truncate(true) - .write(true) - .open(dir.path().join("options.json"))?; - write!(&opts_file, "{}", serialized)?; - - let wasm_path = dir.path().join("rootfs/hello.wasm"); - let mut f = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .mode(0o755) - .open(wasm_path)?; - f.write_all(&wasmbytes)?; - - let stdout = File::create(dir.path().join("stdout"))?; - drop(stdout); - - let spec = SpecBuilder::default() - .root(RootBuilder::default().path("rootfs").build()?) - .process( - ProcessBuilder::default() - .cwd("/") - .args(vec!["./hello.wasm".to_string()]) - .build()?, - ) - .build()?; - - spec.save(dir.path().join("config.json"))?; - - let mut cfg = - InstanceConfig::new((), "test_namespace".into(), "/containerd/address".into()); - let cfg = cfg - .set_bundle(dir.path().to_str().unwrap().to_string()) - .set_stdout(dir.path().join("stdout").to_str().unwrap().to_string()); - - let wasi = Wasi::new("test".to_string(), Some(cfg)); - - wasi.start()?; - - let (tx, rx) = channel(); - let waiter = Wait::new(tx); - wasi.wait(&waiter).unwrap(); - - let res = match rx.recv_timeout(Duration::from_secs(10)) { - Ok(res) => Ok(res), - Err(e) => { - wasi.kill(SIGKILL as u32).unwrap(); - return Err(Error::Others(format!( - "error waiting for module to finish: {0}", - e - ))); - } - }; - wasi.delete()?; - res - } - #[test] #[serial] fn test_delete_after_create() { @@ -246,7 +172,7 @@ mod wasitest { #[test] #[serial] - fn test_wasi() -> Result<(), Error> { + fn test_wasi() -> anyhow::Result<()> { if !has_cap_sys_admin() { println!("running test with sudo: {}", function!()); return run_test_with_sudo(function!()); @@ -256,7 +182,7 @@ mod wasitest { let path = dir.path(); let wasm_bytes = wat2wasm(WASI_HELLO_WAT).unwrap(); - let res = run_wasi_test(&dir, wasm_bytes)?; + let res = run_wasi_test::(&dir, wasm_bytes, None)?; assert_eq!(res.0, 0); @@ -269,7 +195,7 @@ mod wasitest { #[test] #[serial] - fn test_wasi_error() -> Result<(), Error> { + fn test_wasi_error() -> anyhow::Result<()> { if !has_cap_sys_admin() { println!("running test with sudo: {}", function!()); return run_test_with_sudo(function!()); @@ -278,7 +204,7 @@ mod wasitest { let dir = tempdir()?; let wasm_bytes = wat2wasm(WASI_RETURN_ERROR).unwrap(); - let res = run_wasi_test(&dir, wasm_bytes)?; + let res = run_wasi_test::(&dir, wasm_bytes, None)?; // Expect error code from the run. assert_eq!(res.0, 137); diff --git a/crates/containerd-shim-wasmtime/src/instance/instance_linux.rs b/crates/containerd-shim-wasmtime/src/instance/instance_linux.rs index 4c069aa92..1a148a32a 100644 --- a/crates/containerd-shim-wasmtime/src/instance/instance_linux.rs +++ b/crates/containerd-shim-wasmtime/src/instance/instance_linux.rs @@ -90,24 +90,18 @@ impl LibcontainerInstance for Wasi { #[cfg(test)] mod wasitest { - use std::borrow::Cow; - use std::collections::HashMap; - use std::fs::{create_dir, read_to_string, File, OpenOptions}; - use std::io::prelude::*; + + use std::fs::read_to_string; use std::os::fd::RawFd; - use std::os::unix::prelude::OpenOptionsExt; - use std::sync::mpsc::channel; - use std::time::Duration; - use chrono::{DateTime, Utc}; use containerd_shim_wasm::function; - use containerd_shim_wasm::sandbox::instance::Wait; - use containerd_shim_wasm::sandbox::testutil::{has_cap_sys_admin, run_test_with_sudo}; + use containerd_shim_wasm::sandbox::testutil::{ + has_cap_sys_admin, run_test_with_sudo, run_wasi_test, + }; use containerd_shim_wasm::sandbox::Instance; - use libc::{SIGKILL, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; + use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use nix::unistd::dup2; - use oci_spec::runtime::{ProcessBuilder, RootBuilder, SpecBuilder}; - use tempfile::{tempdir, TempDir}; + use tempfile::tempdir; use super::*; @@ -177,7 +171,7 @@ mod wasitest { } #[test] - fn test_wasi_entrypoint() -> Result<(), Error> { + fn test_wasi_entrypoint() -> anyhow::Result<()> { if !has_cap_sys_admin() { println!("running test with sudo: {}", function!()); return run_test_with_sudo(function!()); @@ -191,7 +185,7 @@ mod wasitest { let path = dir.path(); let wasm_bytes = hello_world_module(None); - let res = run_wasi_test(&dir, wasm_bytes.into(), None)?; + let res = run_wasi_test::(&dir, wasm_bytes, None)?; assert_eq!(res.0, 0); @@ -205,7 +199,7 @@ mod wasitest { // ignore until https://github.com/containerd/runwasi/issues/194 is resolved #[test] #[ignore] - fn test_wasi_custom_entrypoint() -> Result<(), Error> { + fn test_wasi_custom_entrypoint() -> anyhow::Result<()> { if !has_cap_sys_admin() { println!("running test with sudo: {}", function!()); return run_test_with_sudo(function!()); @@ -217,7 +211,7 @@ mod wasitest { let path = dir.path(); let wasm_bytes = hello_world_module(Some("foo")); - let res = run_wasi_test(&dir, wasm_bytes.into(), Some("foo"))?; + let res = run_wasi_test::(&dir, wasm_bytes, Some("foo"))?; assert_eq!(res.0, 0); @@ -227,83 +221,4 @@ mod wasitest { reset_stdio(); Ok(()) } - - fn run_wasi_test( - dir: &TempDir, - wasmbytes: Cow<[u8]>, - start_fn: Option<&str>, - ) -> Result<(u32, DateTime), Error> { - create_dir(dir.path().join("rootfs"))?; - let rootdir = dir.path().join("runwasi"); - create_dir(rootdir)?; - let rootdir = PathBuf::from("/path/to/root"); - let mut opts = HashMap::new(); - opts.insert("root", rootdir); - let serialized = serde_json::to_string(&opts)?; - let opts_file = OpenOptions::new() - .read(true) - .create(true) - .truncate(true) - .write(true) - .open(dir.path().join("options.json"))?; - write!(&opts_file, "{}", serialized)?; - - let wasm_path = dir.path().join("rootfs/hello.wat"); - let mut f = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .mode(0o755) - .open(wasm_path)?; - f.write_all(&wasmbytes)?; - - let stdout = File::create(dir.path().join("stdout"))?; - drop(stdout); - - let entrypoint = match start_fn { - Some(s) => "./hello.wat#".to_string() + s, - None => "./hello.wat".to_string(), - }; - let spec = SpecBuilder::default() - .root(RootBuilder::default().path("rootfs").build()?) - .process( - ProcessBuilder::default() - .cwd("/") - .args(vec![entrypoint]) - .build()?, - ) - .build()?; - - spec.save(dir.path().join("config.json"))?; - - let mut cfg = InstanceConfig::new( - Default::default(), - "test_namespace".into(), - "/containerd/address".into(), - ); - let cfg = cfg - .set_bundle(dir.path().to_str().unwrap().to_string()) - .set_stdout(dir.path().join("stdout").to_str().unwrap().to_string()); - - let wasi = Wasi::new("test".to_string(), Some(cfg)); - - wasi.start()?; - - let (tx, rx) = channel(); - let waiter = Wait::new(tx); - wasi.wait(&waiter).unwrap(); - - let res = match rx.recv_timeout(Duration::from_secs(10)) { - Ok(res) => Ok(res), - Err(e) => { - wasi.kill(SIGKILL as u32).unwrap(); - return Err(Error::Others(format!( - "error waiting for module to finish: {0}", - e - ))); - } - }; - wasi.delete()?; - res - } }