Skip to content

Commit

Permalink
wasmedge: OCI spec tests.
Browse files Browse the repository at this point in the history
Signed-off-by: Ismo Puustinen <[email protected]>
  • Loading branch information
ipuustin committed May 4, 2023
1 parent a72617e commit b471f3a
Showing 1 changed file with 286 additions and 0 deletions.
286 changes: 286 additions & 0 deletions crates/containerd-shim-wasmedge/tests/oci_tests.rs
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(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()?,
)
.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(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()])
.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(function!());
}

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(())
}

0 comments on commit b471f3a

Please sign in to comment.