Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for wasmtime #1402

Merged
merged 5 commits into from
Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,030 changes: 1,004 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["tests/rust-integration-tests/*", "crates/*"]
members = ["tests/rust-integration-tests/*", "crates/*", "tools/*"]

[profile.release]
lto = true
7 changes: 5 additions & 2 deletions crates/libcontainer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ keywords = ["youki", "container", "cgroups"]
[features]
default = ["systemd", "v2", "v1"]
wasm-wasmer = ["wasmer", "wasmer-wasi"]
wasm-wasmedge = ["wasmedge-sdk/standalone"]
wasm-wasmtime = ["wasmtime", "wasmtime-wasi"]
systemd = ["libcgroups/systemd", "v2"]
v2 = ["libcgroups/v2"]
v1 = ["libcgroups/v1"]
cgroupsv2_devices = ["libcgroups/cgroupsv2_devices"]
wasm-wasmedge = ["wasmedge-sdk/standalone"]

[dependencies]
anyhow = "1.0"
Expand All @@ -44,7 +45,9 @@ syscalls = "0.6.7"
rust-criu = "0.2.0"
wasmer = { version = "2.2.0", optional = true }
wasmer-wasi = { version = "2.3.0", optional = true }
wasmedge-sdk = { version = "0.7.1", optional = true}
wasmedge-sdk = { version = "0.7.1", optional = true }
wasmtime = {version = "3.0.1", optional = true }
wasmtime-wasi = {version = "3.0.1", optional = true }

[dev-dependencies]
oci-spec = { version = "0.5.8", features = ["proptests", "runtime"] }
Expand Down
11 changes: 5 additions & 6 deletions crates/libcontainer/src/process/container_init_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs::RootFS,
rootless::Rootless, seccomp, tty, utils,
};
use anyhow::{bail, Context, Result};
use anyhow::{bail, Context, Ok, Result};
use nix::mount::MsFlags;
use nix::sched::CloneFlags;
use nix::sys::stat::Mode;
Expand Down Expand Up @@ -279,7 +279,7 @@ pub fn container_init_process(
// This may allow the user running youki to access directories
// that the container user cannot access.
match unistd::chdir(proc.cwd()) {
Ok(_) => false,
std::result::Result::Ok(_) => false,
Err(nix::Error::EPERM) => true,
Err(e) => bail!("failed to chdir: {}", e),
}
Expand All @@ -299,9 +299,9 @@ pub fn container_init_process(
// not 0, then we have to preserve those fds as well, and set up the correct
// environment variables.
let preserve_fds: i32 = match env::var("LISTEN_FDS") {
Ok(listen_fds_str) => {
std::result::Result::Ok(listen_fds_str) => {
let listen_fds = match listen_fds_str.parse::<i32>() {
Ok(v) => v,
std::result::Result::Ok(v) => v,
Err(error) => {
log::warn!(
"LISTEN_FDS entered is not a fd. Ignore the value. {:?}",
Expand Down Expand Up @@ -442,8 +442,7 @@ pub fn container_init_process(
}

if proc.args().is_some() {
ExecutorManager::exec(spec)?;
unreachable!("process image should have been replaced after exec");
ExecutorManager::exec(spec)
} else {
bail!("on non-Windows, at least one process arg entry is required")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub fn container_intermediate_process(
.close()
.context("failed to close sender in the intermediate process")?;
match container_init_process(args, main_sender, init_receiver) {
Ok(_) => unreachable!("successful exec should never reach here"),
Ok(_) => Ok(0),
Err(e) => {
if let ContainerType::TenantContainer { exec_notify_fd } = args.container_type {
let buf = format!("{}", e);
Expand Down
9 changes: 9 additions & 0 deletions crates/libcontainer/src/workload/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ use self::default::DefaultExecutor;
use self::wasmedge::WasmEdgeExecutor;
#[cfg(feature = "wasm-wasmer")]
use self::wasmer::WasmerExecutor;
#[cfg(feature = "wasm-wasmtime")]
use self::wasmtime::WasmtimeExecutor;

pub mod default;
#[cfg(feature = "wasm-wasmedge")]
pub mod wasmedge;
#[cfg(feature = "wasm-wasmer")]
pub mod wasmer;
#[cfg(feature = "wasm-wasmtime")]
pub mod wasmtime;

static EMPTY: Vec<String> = Vec::new();

Expand All @@ -37,6 +41,11 @@ impl ExecutorManager {
return WasmEdgeExecutor::exec(spec).context("wasmedge execution failed");
}

#[cfg(feature = "wasm-wasmtime")]
if WasmtimeExecutor::can_handle(spec)? {
return WasmtimeExecutor::exec(spec).context("wasmtime execution failed");
}

DefaultExecutor::exec(spec).context("default execution failed")
}
}
3 changes: 2 additions & 1 deletion crates/libcontainer/src/workload/wasmer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ impl Executor for WasmerExecutor {
.finalize()?;

let store = Store::default();
let module = Module::from_file(&store, &args[0]).context("could not load wasm module")?;
let module = Module::from_file(&store, &args[0])
.with_context(|| format!("could not load wasm module from {}", &args[0]))?;

let imports = wasm_env
.import_object(&module)
Expand Down
96 changes: 96 additions & 0 deletions crates/libcontainer/src/workload/wasmtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use anyhow::{anyhow, bail, Context, Result};
use oci_spec::runtime::Spec;
use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::WasiCtxBuilder;

use super::{Executor, EMPTY};

const EXECUTOR_NAME: &str = "wasmtime";

pub struct WasmtimeExecutor {}

impl Executor for WasmtimeExecutor {
fn exec(spec: &Spec) -> Result<()> {
log::info!("Executing workload with wasmtime handler");
let process = spec.process().as_ref();

let args = spec
.process()
.as_ref()
.and_then(|p| p.args().as_ref())
.unwrap_or(&EMPTY);
if args.is_empty() {
bail!("at least one process arg must be specified")
}

if !args[0].ends_with(".wasm") && !args[0].ends_with(".wat") {
bail!(
"first argument must be a wasm or wat module, but was {}",
args[0]
)
}

let mut cmd = args[0].clone();
let stripped = args[0].strip_prefix(std::path::MAIN_SEPARATOR);
if let Some(cmd_stripped) = stripped {
cmd = cmd_stripped.to_string();
}

let envs: Vec<(String, String)> = process
.and_then(|p| p.env().as_ref())
.unwrap_or(&EMPTY)
.iter()
.filter_map(|e| {
e.split_once('=')
.map(|kv| (kv.0.trim().to_string(), kv.1.trim().to_string()))
})
.collect();

let engine = Engine::default();
let module = Module::from_file(&engine, &cmd)
.with_context(|| format!("could not load wasm module from {}", &cmd))?;

let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |s| s)
.context("cannot add wasi context to linker")?;

let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.args(args)
.context("cannot add args to wasi context")?
.envs(&envs)
.context("cannot add environment variables to wasi context")?
.build();

let mut store = Store::new(&engine, wasi);

let instance = linker
.instantiate(&mut store, &module)
.context("wasm module could not be instantiated")?;
let start = instance
.get_func(&mut store, "_start")
.ok_or_else(|| anyhow!("could not retrieve wasm module main function"))?;

start
.call(&mut store, &[], &mut [])
.context("wasm module was not executed successfully")
}

fn can_handle(spec: &Spec) -> Result<bool> {
if let Some(annotations) = spec.annotations() {
if let Some(handler) = annotations.get("run.oci.handler") {
return Ok(handler == "wasm");
}

if let Some(variant) = annotations.get("module.wasm.image/variant") {
return Ok(variant == "compat");
}
}

Ok(false)
}

fn name() -> &'static str {
EXECUTOR_NAME
}
}
3 changes: 3 additions & 0 deletions crates/youki/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ systemd = ["libcgroups/systemd", "libcontainer/systemd", "v2"]
v2 = ["libcgroups/v2", "libcontainer/v2"]
v1 = ["libcgroups/v1", "libcontainer/v1"]
cgroupsv2_devices = ["libcgroups/cgroupsv2_devices", "libcontainer/cgroupsv2_devices"]
wasm-wasmer = ["libcontainer/wasm-wasmer"]
wasm-wasmedge = ["libcontainer/wasm-wasmedge"]
wasm-wasmtime = ["libcontainer/wasm-wasmtime"]

[dependencies.clap]
version = "3.2.23"
Expand Down
8 changes: 8 additions & 0 deletions tools/wasm-sample/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "wasm-sample"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
25 changes: 25 additions & 0 deletions tools/wasm-sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
This is a simple wasm module for testing purposes. It prints out the arguments given to the program and all environment variables. You can compile the module with

```
cargo build --target wasm32-wasi
```

If you want youki to execute the module you must copy it to the root file system of the container and reference it in the args of the config.json. You must also ensure that the annotations contain `"run.oci.handler": "wasm"` and that youki has been compiled with one of the supported wasm runtimes. For further information please check the [documentation](https://containers.github.io/youki/user/webassembly.html).

```
"ociVersion": "1.0.2-dev",
"annotations": {
"run.oci.handler": "wasm"
},
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"/wasm-sample.wasm",
"hello",
"wasm"
],
```
11 changes: 11 additions & 0 deletions tools/wasm-sample/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fn main() {
utam0k marked this conversation as resolved.
Show resolved Hide resolved
println!("Printing args");
for arg in std::env::args().skip(1) {
println!("{}", arg);
}

println!("Printing envs");
for envs in std::env::vars() {
println!("{:?}", envs);
}
}