-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tests which use external Wasm modules to test the OCI spec features. Signed-off-by: Ismo Puustinen <[email protected]>
- Loading branch information
Showing
1 changed file
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
use std::borrow::Cow; | ||
use std::fs::{create_dir, read_to_string, File, OpenOptions}; | ||
use std::io::prelude::*; | ||
use std::os::unix::fs::OpenOptionsExt; | ||
use std::path::{Path, PathBuf}; | ||
use std::sync::mpsc::channel; | ||
use std::sync::Arc; | ||
use std::time::Duration; | ||
|
||
use chrono::{DateTime, Utc}; | ||
use libc::SIGKILL; | ||
use serde::{Deserialize, Serialize}; | ||
use serial_test::serial; | ||
use tempfile::{tempdir, TempDir}; | ||
|
||
use oci_spec::runtime::{ | ||
LinuxBuilder, LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder, ProcessBuilder, | ||
RootBuilder, Spec, SpecBuilder, | ||
}; | ||
|
||
use containerd_shim_wasm::function; | ||
use containerd_shim_wasm::sandbox::exec::has_cap_sys_admin; | ||
use containerd_shim_wasm::sandbox::instance::Wait; | ||
use containerd_shim_wasm::sandbox::testutil::run_test_with_sudo; | ||
use containerd_shim_wasm::sandbox::{EngineGetter, Error, Instance, InstanceConfig}; | ||
use containerd_shim_wasmedge::instance::{reset_stdio, Wasi}; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
struct Options { | ||
root: Option<PathBuf>, | ||
} | ||
|
||
fn get_external_wasm_module(name: String) -> Result<Vec<u8>, Error> { | ||
let manifest_dir = env!("CARGO_MANIFEST_DIR"); | ||
let target = Path::new(manifest_dir) | ||
.join("../../target/wasm32-wasi/debug") | ||
.join(name.clone()); | ||
std::fs::read(target).map_err(|e| { | ||
Error::Others(format!( | ||
"failed to read requested Wasm module ({}): {}. Perhaps you need to run 'make test/wasm-modules' first.", | ||
name, e | ||
)) | ||
}) | ||
} | ||
|
||
fn run_wasi_test_with_spec( | ||
dir: &TempDir, | ||
spec: &Spec, | ||
wasmbytes: Cow<[u8]>, | ||
) -> Result<(u32, DateTime<Utc>), Error> { | ||
create_dir(dir.path().join("rootfs"))?; | ||
let rootdir = dir.path().join("runwasi"); | ||
create_dir(&rootdir)?; | ||
let opts = Options { | ||
root: Some(rootdir), | ||
}; | ||
let opts_file = OpenOptions::new() | ||
.read(true) | ||
.create(true) | ||
.truncate(true) | ||
.write(true) | ||
.open(dir.path().join("options.json"))?; | ||
write!(&opts_file, "{}", serde_json::to_string(&opts)?)?; | ||
|
||
let wasm_path = dir.path().join("rootfs/file.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); | ||
|
||
spec.save(dir.path().join("config.json"))?; | ||
|
||
let mut cfg = InstanceConfig::new(Wasi::new_engine()?, "test_namespace".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 = Arc::new(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_external_hello_world() -> Result<(), Error> { | ||
if !has_cap_sys_admin() { | ||
println!("running test with sudo: {}", function!()); | ||
return run_test_with_sudo("test_external_hello_world"); | ||
} | ||
|
||
let dir = tempdir()?; | ||
let path = dir.path(); | ||
|
||
let wasmbytes = get_external_wasm_module("hello-world.wasm".to_string())?; | ||
|
||
let spec = SpecBuilder::default() | ||
.root(RootBuilder::default().path("rootfs").build()?) | ||
.process( | ||
ProcessBuilder::default() | ||
.cwd("/") | ||
.args(vec!["./file.wasm".to_string()]) | ||
.build()?, | ||
) | ||
.build()?; | ||
|
||
let res = run_wasi_test_with_spec(&dir, &spec, Cow::from(wasmbytes))?; | ||
|
||
assert_eq!(res.0, 0); | ||
|
||
let output = read_to_string(path.join("stdout"))?; | ||
assert!(output.starts_with("hello world")); | ||
|
||
reset_stdio(); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
#[serial] | ||
fn test_seccomp_hello_world_pass() -> Result<(), Error> { | ||
if !has_cap_sys_admin() { | ||
println!("running test with sudo: {}", function!()); | ||
return run_test_with_sudo("test_seccomp_hello_world_pass"); | ||
} | ||
|
||
let dir = tempdir()?; | ||
let path = dir.path(); | ||
|
||
let wasmbytes = get_external_wasm_module("hello-world.wasm".to_string())?; | ||
|
||
let spec = SpecBuilder::default() | ||
.root(RootBuilder::default().path("rootfs").build()?) | ||
.process( | ||
ProcessBuilder::default() | ||
.cwd("/") | ||
.args(vec!["./file.wasm".to_string()]) | ||
.build()?, | ||
) | ||
.linux( | ||
LinuxBuilder::default() | ||
.seccomp( | ||
LinuxSeccompBuilder::default() | ||
.default_action(LinuxSeccompAction::ScmpActAllow) | ||
.architectures(vec![oci_spec::runtime::Arch::ScmpArchNative]) | ||
.syscalls(vec![LinuxSyscallBuilder::default() | ||
.names(vec!["getcwd".to_string()]) | ||
.action(LinuxSeccompAction::ScmpActAllow) | ||
.build()?]) | ||
.build()?, | ||
) | ||
.build()?, | ||
) | ||
.build()?; | ||
|
||
let res = run_wasi_test_with_spec(&dir, &spec, Cow::from(wasmbytes))?; | ||
|
||
assert_eq!(res.0, 0); | ||
|
||
let output = read_to_string(path.join("stdout"))?; | ||
assert!(output.starts_with("hello world")); | ||
|
||
reset_stdio(); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
#[serial] | ||
fn test_seccomp_hello_world_fail() -> Result<(), Error> { | ||
if !has_cap_sys_admin() { | ||
println!("running test with sudo: {}", function!()); | ||
return run_test_with_sudo("test_seccomp_hello_world_fail"); | ||
} | ||
|
||
let dir = tempdir()?; | ||
|
||
let wasmbytes = get_external_wasm_module("hello-world.wasm".to_string())?; | ||
|
||
let spec = SpecBuilder::default() | ||
.root(RootBuilder::default().path("rootfs").build()?) | ||
.process( | ||
ProcessBuilder::default() | ||
.cwd("/") | ||
.args(vec!["./file.wasm".to_string()]) | ||
.build()?, | ||
) | ||
.linux( | ||
LinuxBuilder::default() | ||
.seccomp( | ||
LinuxSeccompBuilder::default() | ||
.default_action(LinuxSeccompAction::ScmpActAllow) | ||
.architectures(vec![oci_spec::runtime::Arch::ScmpArchNative]) | ||
.syscalls(vec![LinuxSyscallBuilder::default() | ||
.names(vec!["getcwd".to_string()]) // Do not allow getcwd() | ||
.action(LinuxSeccompAction::ScmpActErrno) | ||
.build()?]) | ||
.build()?, | ||
) | ||
.build()?, | ||
) | ||
.build()?; | ||
|
||
let res = run_wasi_test_with_spec(&dir, &spec, Cow::from(wasmbytes))?; | ||
|
||
assert_ne!(res.0, 0); // Returns an error | ||
|
||
reset_stdio(); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
#[serial] | ||
#[ignore] | ||
fn test_seccomp_hello_world_notify() -> Result<(), Error> { | ||
// Test how seccomp works together with an external notification agent. | ||
// Configure the external agent to use socket /tmp/seccomp-agent.socket | ||
// and set it to either allow or decline (with error) "writev" system | ||
// call. | ||
|
||
if !has_cap_sys_admin() { | ||
println!("running test with sudo: {}", function!()); | ||
return run_test_with_sudo(function!()); | ||
} | ||
|
||
let dir = tempdir()?; | ||
let path = dir.path(); | ||
|
||
let wasmbytes = get_external_wasm_module("hello-world.wasm".to_string())?; | ||
|
||
let spec = SpecBuilder::default() | ||
.root(RootBuilder::default().path("rootfs").build()?) | ||
.process( | ||
ProcessBuilder::default() | ||
.cwd("/") | ||
.args(vec!["./file.wasm".to_string()]) | ||
.build()?, | ||
) | ||
.linux( | ||
LinuxBuilder::default() | ||
.seccomp( | ||
LinuxSeccompBuilder::default() | ||
.default_action(LinuxSeccompAction::ScmpActAllow) | ||
.architectures(vec![oci_spec::runtime::Arch::ScmpArchNative]) | ||
.syscalls(vec![LinuxSyscallBuilder::default() | ||
.names(vec!["getcwd".to_string()]) // system call 72 | ||
.action(LinuxSeccompAction::ScmpActNotify) | ||
.build()?]) | ||
.listener_path("/tmp/seccomp-agent.socket") | ||
.build()?, | ||
) | ||
.build()?, | ||
) | ||
.build()?; | ||
|
||
let res = run_wasi_test_with_spec(&dir, &spec, Cow::from(wasmbytes))?; | ||
|
||
assert_eq!(res.0, 0); // Returns success or error, depending on how the external agent is configured | ||
|
||
let output = read_to_string(path.join("stdout"))?; | ||
assert!(output.starts_with("hello world")); | ||
|
||
reset_stdio(); | ||
|
||
Ok(()) | ||
} |