diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ed98f265..993de6be0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - runtime: ["common", "wasmedge", "wasmtime", "wasmer"] + runtime: ["common", "wasmedge", "wasmtime", "wasmer", "wamr"] uses: ./.github/workflows/action-check.yml with: os: ${{ matrix.os }} @@ -53,7 +53,7 @@ jobs: strategy: matrix: os: ["ubuntu-22.04"] - runtime: ["common", "wasmtime", "wasmedge", "wasmer"] + runtime: ["common", "wasmtime", "wasmedge", "wasmer", "wamr"] libc: ["musl", "gnu"] arch: ["x86_64", "aarch64"] uses: ./.github/workflows/action-build.yml @@ -85,7 +85,7 @@ jobs: matrix: # 20.04 uses cgroupv1, 22.04 uses cgroupv2 os: ["ubuntu-20.04", "ubuntu-22.04"] - runtime: ["wasmtime", "wasmedge", "wasmer"] + runtime: ["wasmtime", "wasmedge", "wasmer", "wamr"] uses: ./.github/workflows/action-test-smoke.yml with: os: ${{ matrix.os }} @@ -99,7 +99,7 @@ jobs: matrix: # 20.04 uses cgroupv1, 22.04 uses cgroupv2 os: ["ubuntu-20.04", "ubuntu-22.04"] - runtime: ["wasmtime", "wasmedge", "wasmer"] + runtime: ["wasmtime", "wasmedge", "wasmer", "wamr"] uses: ./.github/workflows/action-test-kind.yml with: os: ${{ matrix.os }} @@ -112,7 +112,7 @@ jobs: strategy: matrix: os: ["ubuntu-22.04"] - runtime: ["wasmtime", "wasmedge", "wasmer"] + runtime: ["wasmtime", "wasmedge", "wasmer", "wamr"] uses: ./.github/workflows/action-test-kind.yml with: os: ${{ matrix.os }} @@ -127,7 +127,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-20.04", "ubuntu-22.04"] - runtime: ["wasmtime", "wasmedge", "wasmer"] + runtime: ["wasmtime", "wasmedge", "wasmer", "wamr"] uses: ./.github/workflows/action-test-k3s.yml with: os: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d305c0e02..42c4c8289 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ on: - containerd-shim-wasmer - containerd-shim-wasmedge - containerd-shim-wasmtime + - containerd-shim-wamr version: description: "The version of the crate to release. (e.g., 1.2.3)" type: string diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1179d48fb..37e450214 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ There are several projects in the repository: - `containerd-shim-wasm` - main library that is used by runtimes to create shims. Most of the shared code lives here. - `containerd-shim-wasm-test-modules` - library with wasm test modules used in testing framework -- `containerd-shim-` - shims per runtime (wasmtime, wasmedge, wasmer, etc). These produce binaries that are the shims which containerd talks too. +- `containerd-shim-` - shims per runtime (wasmtime, wasmedge, wasmer, wamr, etc). These produce binaries that are the shims which containerd talks too. - `oci-tar-builder` - library and executable that helps build OCI tar files. - `wasi-demo-app` - wasm application that is used for demos and testing. @@ -55,7 +55,7 @@ To build all the shims in this repository: make build ``` -To build a shim for specific runtime (wasmtime, wasmer, wasmedge, etc): +To build a shim for specific runtime (wasmtime, wasmer, wasmedge, wamr, etc): ``` make build- diff --git a/Cargo.lock b/Cargo.lock index 47865e7a4..6bcc66e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,26 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.87", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -726,6 +746,17 @@ dependencies = [ "ttrpc-codegen", ] +[[package]] +name = "containerd-shim-wamr" +version = "0.4.0" +dependencies = [ + "anyhow", + "containerd-shim-wasm", + "log", + "serial_test", + "wamr-rust-sdk", +] + [[package]] name = "containerd-shim-wasm" version = "0.7.0" @@ -5564,6 +5595,24 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wamr-rust-sdk" +version = "1.0.0" +source = "git+https://github.com/bytecodealliance/wamr-rust-sdk?tag=v1.1.0#b03ec6030b3f0ee9a190e59804ed87b2445635f6" +dependencies = [ + "wamr-sys", +] + +[[package]] +name = "wamr-sys" +version = "1.0.0" +source = "git+https://github.com/bytecodealliance/wamr-rust-sdk?tag=v1.1.0#b03ec6030b3f0ee9a190e59804ed87b2445635f6" +dependencies = [ + "bindgen 0.70.1", + "cc", + "cmake", +] + [[package]] name = "want" version = "0.3.1" @@ -5763,7 +5812,7 @@ version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d8e2276d63bb6f0c36871218643d193d2da6da3db36c1c1227547da465ed58" dependencies = [ - "bindgen", + "bindgen 0.69.5", "cfg-if 1.0.0", "cmake", "flate2", diff --git a/Cargo.toml b/Cargo.toml index e795c126e..d39339ae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/containerd-shim-wasmedge", "crates/containerd-shim-wasmtime", "crates/containerd-shim-wasmer", + "crates/containerd-shim-wamr", "benches/containerd-shim-benchmarks", ] resolver = "2" diff --git a/Makefile b/Makefile index 9a3211c6c..310e83610 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PREFIX ?= /usr/local INSTALL ?= install CARGO ?= cargo TEST_IMG_NAME ?= wasmtest:latest -RUNTIMES ?= wasmedge wasmtime wasmer +RUNTIMES ?= wasmedge wasmtime wasmer wamr CONTAINERD_NAMESPACE ?= default RUSTC ?= rustc diff --git a/README.md b/README.md index d24400d2a..8c0084ec8 100644 --- a/README.md +++ b/README.md @@ -121,10 +121,10 @@ Check out these projects that build on top of runwasi: ### Components -- **containerd-shim-[ wasmedge | wasmtime | wasmer ]-v1** +- **containerd-shim-[ wasmedge | wasmtime | wasmer | wamr ]-v1** This is a containerd shim which runs wasm workloads in [WasmEdge](https://github.com/WasmEdge/WasmEdge) or [Wasmtime](https://github.com/bytecodealliance/wasmtime) or [Wasmer](https://github.com/wasmerio/wasmer). -You can use it with containerd's `ctr` by specifying `--runtime=io.containerd.[ wasmedge | wasmtime | wasmer ].v1` when creating the container. +You can use it with containerd's `ctr` by specifying `--runtime=io.containerd.[ wasmedge | wasmtime | wasmer | wamr ].v1` when creating the container. And make sure the shim binary must be in $PATH (that is the $PATH that containerd sees). Usually you just run `make install` after `make build`. > build shim with wasmedge we need install library first @@ -156,7 +156,7 @@ make load ### Demo 1 using container image that contains a Wasm module. -Run it with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer ].v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm echo 'hello'`. You should see some output repeated like: +Run it with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer | wamr ].v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm echo 'hello'`. You should see some output repeated like: ```terminal sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm @@ -193,7 +193,7 @@ make test-image/oci make load/oci ``` -Run the image with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer ].v1 ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci` +Run the image with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer | wamr ].v1 ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci` ``` sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci wasi-demo-oci.wasm echo 'hello' diff --git a/crates/containerd-shim-wamr/Cargo.toml b/crates/containerd-shim-wamr/Cargo.toml new file mode 100644 index 000000000..a913111f7 --- /dev/null +++ b/crates/containerd-shim-wamr/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "containerd-shim-wamr" +version.workspace = true +edition.workspace = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +containerd-shim-wasm = { workspace = true } +log = { workspace = true } + +[target.'cfg(unix)'.dependencies] +wamr-rust-sdk = { git = "https://github.com/bytecodealliance/wamr-rust-sdk", tag = "v1.1.0" } + +[dev-dependencies] +containerd-shim-wasm = { workspace = true, features = ["testing"] } +serial_test = { workspace = true } + +[[bin]] +name = "containerd-shim-wamr-v1" +path = "src/main.rs" diff --git a/crates/containerd-shim-wamr/src/instance.rs b/crates/containerd-shim-wamr/src/instance.rs new file mode 100644 index 000000000..5e8231419 --- /dev/null +++ b/crates/containerd-shim-wamr/src/instance.rs @@ -0,0 +1,93 @@ +use anyhow::{Context, Result}; +use containerd_shim_wasm::container::{Engine, Entrypoint, Instance, RuntimeContext, Stdio}; +use wamr_rust_sdk::function::Function; +use wamr_rust_sdk::instance::Instance as WamrInst; +use wamr_rust_sdk::module::Module; +use wamr_rust_sdk::runtime::Runtime; +use wamr_rust_sdk::wasi_context::WasiCtxBuilder; + +pub type WamrInstance = Instance; + +pub struct WamrEngine { + runtime: Runtime, +} + +unsafe impl Send for WamrEngine {} +unsafe impl Sync for WamrEngine {} + +// TODO: wasmr_rust_sdk::runtime::Runtime should implement Clone + +impl Default for WamrEngine { + fn default() -> Self { + let runtime = Runtime::new().unwrap(); + Self { runtime } + } +} + +impl Clone for WamrEngine { + fn clone(&self) -> Self { + let runtime = Runtime::new().unwrap(); + Self { runtime } + } +} + +impl Engine for WamrEngine { + fn name() -> &'static str { + "wamr" + } + + fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result { + let args = ctx.args(); + let envs = ctx.envs(); + let Entrypoint { + source, func, name, .. + } = ctx.entrypoint(); + + let wasm_bytes = source + .as_bytes() + .context("Failed to get bytes from source")?; + + log::info!("Create a WAMR module"); + + // TODO: error handling isn't ideal + + let mod_name = name.unwrap_or_else(|| "main".to_string()); + + let mut module = Module::from_buf(&self.runtime, &wasm_bytes, &mod_name) + .context("Failed to create module from bytes")?; + + log::info!("Create a WASI context"); + + let wasi_ctx = WasiCtxBuilder::new() + .set_pre_open_path(vec!["/"], vec![]) + .set_env_vars(envs.iter().map(String::as_str).collect()) + .set_arguments(args.iter().map(String::as_str).collect()) + .build(); + + module.set_wasi_context(wasi_ctx); + + // TODO: no way to register a named module with bytes? + + log::info!("Create a WAMR instance"); + + let instance = WamrInst::new(&self.runtime, &module, 1024 * 64) + .context("Failed to create instance")?; + + log::info!("redirect stdio"); + stdio.redirect()?; + + log::info!("Running {func:?}"); + let function = + Function::find_export_func(&instance, &func).context("Failed to find function")?; + let status = function + .call(&instance, &vec![]) + .map(|_| 0) + .map_err(|err| { + log::error!("Error: {:?}", err); + err + }) + .context("Failed to call function")?; + + Ok(status) + } +} diff --git a/crates/containerd-shim-wamr/src/lib.rs b/crates/containerd-shim-wamr/src/lib.rs new file mode 100644 index 000000000..88df69337 --- /dev/null +++ b/crates/containerd-shim-wamr/src/lib.rs @@ -0,0 +1,10 @@ +#[cfg(unix)] +pub mod instance; + +#[cfg(unix)] +pub use instance::WamrInstance; + +#[cfg(unix)] +#[cfg(test)] +#[path = "tests.rs"] +mod wamr_tests; diff --git a/crates/containerd-shim-wamr/src/main.rs b/crates/containerd-shim-wamr/src/main.rs new file mode 100644 index 000000000..0b401db79 --- /dev/null +++ b/crates/containerd-shim-wamr/src/main.rs @@ -0,0 +1,13 @@ +#[cfg(not(target_os = "windows"))] +use containerd_shim_wamr::WamrInstance; +use containerd_shim_wasm::sandbox::cli::{revision, shim_main, version}; + +#[cfg(target_os = "windows")] +fn main() { + panic!("WAMR shim is not supported on Windows"); +} + +#[cfg(not(target_os = "windows"))] +fn main() { + shim_main::("wamr", version!(), revision!(), "v1", None); +} diff --git a/crates/containerd-shim-wamr/src/tests.rs b/crates/containerd-shim-wamr/src/tests.rs new file mode 100644 index 000000000..18973f750 --- /dev/null +++ b/crates/containerd-shim-wamr/src/tests.rs @@ -0,0 +1,119 @@ +use std::time::Duration; + +//use containerd_shim_wasm::sandbox::Instance; +use containerd_shim_wasm::testing::modules::*; +use containerd_shim_wasm::testing::WasiTest; +use serial_test::serial; + +use crate::instance::WamrInstance as WasiInstance; + +#[test] +#[serial] +fn test_delete_after_create() -> anyhow::Result<()> { + WasiTest::::builder()?.build()?.delete()?; + Ok(()) +} + +#[test] +#[serial] +fn test_hello_world() -> anyhow::Result<()> { + let (exit_code, stdout, _) = WasiTest::::builder()? + .with_wasm(HELLO_WORLD)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 0); + assert_eq!(stdout, "hello world\n"); + + Ok(()) +} + +#[test] +#[serial] +fn test_hello_world_oci() -> anyhow::Result<()> { + let (builder, _oci_cleanup) = WasiTest::::builder()? + .with_wasm(HELLO_WORLD)? + .as_oci_image(None, None)?; + + let (exit_code, stdout, _) = builder.build()?.start()?.wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 0); + assert_eq!(stdout, "hello world\n"); + + Ok(()) +} +#[test] +#[serial] +fn test_unreachable() -> anyhow::Result<()> { + let (exit_code, _, _) = WasiTest::::builder()? + .with_wasm(UNREACHABLE)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_ne!(exit_code, 0); + + Ok(()) +} + +#[test] +#[serial] +fn test_seccomp() -> anyhow::Result<()> { + let (exit_code, stdout, _) = WasiTest::::builder()? + .with_wasm(SECCOMP)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 0); + assert_eq!(stdout.trim(), "current working dir: /"); + + Ok(()) +} + +#[test] +#[serial] +fn test_has_default_devices() -> anyhow::Result<()> { + let (exit_code, _, _) = WasiTest::::builder()? + .with_wasm(HAS_DEFAULT_DEVICES)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 0); + + Ok(()) +} + +#[test] +#[ignore = "disabled because the WAMR SDK doesn't expose exit code yet"] +// See https://github.com/containerd/runwasi/pull/716#discussion_r1827086060 +fn test_exit_code() -> anyhow::Result<()> { + let (exit_code, _, _) = WasiTest::::builder()? + .with_wasm(EXIT_CODE)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 42); + + Ok(()) +} + +#[test] +#[ignore] +// See https://github.com/containerd/runwasi/pull/716#issuecomment-2458200081 +fn test_custom_entrypoint() -> anyhow::Result<()> { + let (exit_code, stdout, _) = WasiTest::::builder()? + .with_start_fn("foo") + .with_wasm(CUSTOM_ENTRYPOINT)? + .build()? + .start()? + .wait(Duration::from_secs(10))?; + + assert_eq!(exit_code, 0); + assert_eq!(stdout, "hello world\n"); + + Ok(()) +}