Skip to content

Commit

Permalink
Move logic to entry point
Browse files Browse the repository at this point in the history
Signed-off-by: James Sturtevant <[email protected]>
  • Loading branch information
jsturtevant committed Nov 17, 2023
1 parent 8878701 commit 1f5d6f3
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 68 deletions.
94 changes: 65 additions & 29 deletions crates/containerd-shim-wasm/src/container/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,34 @@ pub trait RuntimeContext {
// "/app/app.wasm#entry" -> { path: "/app/app.wasm", func: "entry" }
// "my_module.wat" -> { path: "my_module.wat", func: "_start" }
// "#init" -> { path: "", func: "init" }
fn entrypoint(&self) -> WasiEntrypoint;

fn wasi_loading_strategy(&self) -> WasiLoadingStrategy;
fn entrypoint(&self) -> Entrypoint;

// the platform for the container using the struct defined on the OCI spec definition
// https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-index.md
fn platform(&self) -> &Platform;
}

pub enum WasiLoadingStrategy<'a> {
/// The source for a WASI module / components.
pub enum Source<'a> {
// The WASI module is a file in the file system.
File(PathBuf),
// The WASI module / component is provided as a layer in the OCI spec.
// For a WASI preview 1 module this is usually a single element array.
// For a WASI preview 2 component this is an array of one or more
// elements, where each element is a component.
// Runtimes can additionally provide a list of layer types they support,
// and they will be included in this array, e.g., a `toml` file with the
// runtime configuration.
Oci(&'a [WasmLayer]),
}

pub struct WasiEntrypoint<'a> {
pub path: PathBuf,
/// The entrypoint for a WASI module / component.
///
pub struct Entrypoint<'a> {
pub func: String,
pub name: Option<String>,
pub arg0: Option<&'a Path>,
pub source: Source<'a>,
}

pub(crate) struct WasiContext<'a> {
Expand All @@ -52,25 +64,29 @@ impl RuntimeContext for WasiContext<'_> {
.unwrap_or_default()
}

fn entrypoint(&self) -> WasiEntrypoint {
fn entrypoint(&self) -> Entrypoint {
let arg0 = self.args().first();

let entry_point = arg0.map(String::as_str).unwrap_or("");
let (path, func) = entry_point
.split_once('#')
.unwrap_or((entry_point, "_start"));
WasiEntrypoint {
path: PathBuf::from(path),
func: func.to_string(),
arg0: arg0.map(Path::new),
}
}

fn wasi_loading_strategy(&self) -> WasiLoadingStrategy {
if self.wasm_layers.is_empty() {
WasiLoadingStrategy::File(self.entrypoint().path.clone())
let source = if self.wasm_layers.is_empty() {
Source::File(PathBuf::from(path))
} else {
WasiLoadingStrategy::Oci(self.wasm_layers)
Source::Oci(self.wasm_layers)
};

let module_name = PathBuf::from(path)
.file_stem()
.map(|name| name.to_string_lossy().to_string());

Entrypoint {
func: func.to_string(),
arg0: arg0.map(Path::new),
source,
name: module_name,
}
}

Expand Down Expand Up @@ -175,8 +191,11 @@ mod tests {
platform: &Platform::default(),
};

let path = ctx.entrypoint().path;
assert!(path.as_os_str().is_empty());
let path = ctx.entrypoint().source;
assert!(matches!(
path,
Source::File(p) if p.as_os_str().is_empty()
));

Ok(())
}
Expand All @@ -203,10 +222,20 @@ mod tests {
platform: &Platform::default(),
};

let WasiEntrypoint { path, func, arg0 } = ctx.entrypoint();
assert_eq!(path, Path::new("hello.wat"));
let expected_path = PathBuf::from("hello.wat");
let Entrypoint {
name,
func,
arg0,
source,
} = ctx.entrypoint();
assert_eq!(name, Some("hello".to_string()));
assert_eq!(func, "foo");
assert_eq!(arg0, Some(Path::new("hello.wat#foo")));
assert!(matches!(
source,
Source::File(p) if p == expected_path
));

Ok(())
}
Expand All @@ -233,10 +262,20 @@ mod tests {
platform: &Platform::default(),
};

let WasiEntrypoint { path, func, arg0 } = ctx.entrypoint();
assert_eq!(path, Path::new("/root/hello.wat"));
let expected_path = PathBuf::from("/root/hello.wat");
let Entrypoint {
name,
func,
arg0,
source,
} = ctx.entrypoint();
assert_eq!(name, Some("hello".to_string()));
assert_eq!(func, "_start");
assert_eq!(arg0, Some(Path::new("/root/hello.wat")));
assert!(matches!(
source,
Source::File(p) if p == expected_path
));

Ok(())
}
Expand Down Expand Up @@ -265,8 +304,8 @@ mod tests {

let expected_path = PathBuf::from("/root/hello.wat");
assert!(matches!(
ctx.wasi_loading_strategy(),
WasiLoadingStrategy::File(p) if p == expected_path
ctx.entrypoint().source,
Source::File(p) if p == expected_path
));

Ok(())
Expand Down Expand Up @@ -297,10 +336,7 @@ mod tests {
platform: &Platform::default(),
};

assert!(matches!(
ctx.wasi_loading_strategy(),
WasiLoadingStrategy::Oci(_)
));
assert!(matches!(ctx.entrypoint().source, Source::Oci(_)));

Ok(())
}
Expand Down
20 changes: 16 additions & 4 deletions crates/containerd-shim-wasm/src/container/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::io::Read;

use anyhow::{Context, Result};

use super::Source;
use crate::container::{PathResolve, RuntimeContext};
use crate::sandbox::Stdio;

Expand All @@ -16,13 +17,18 @@ pub trait Engine: Clone + Send + Sync + 'static {
/// Check that the runtime can run the container.
/// This checks runs after the container creation and before the container starts.
/// By it checks that the wasi_entrypoint is either:
/// * a OCI image with wasm layers
/// * a file with the `wasm` filetype header
/// * a parsable `wat` file.
fn can_handle(&self, ctx: &impl RuntimeContext) -> Result<()> {
let path = ctx
.entrypoint()
.path
.resolve_in_path_or_cwd()
let source = ctx.entrypoint().source;

let path = match source {
Source::File(path) => path,
Source::Oci(_) => return Ok(()),
};

path.resolve_in_path_or_cwd()
.next()
.context("module not found")?;

Expand All @@ -37,6 +43,12 @@ pub trait Engine: Clone + Send + Sync + 'static {
Ok(())
}

/// Return the supported OCI layer types
/// This is used to filter only layers that are supported by the runtime.
/// The default implementation returns the OCI layer type 'application/vnd.bytecodealliance.wasm.component.layer.v0+wasm'
/// for WASM modules which can be contain with wasip1 or wasip2 components.
/// Runtimes can override this to support other layer types
/// such as lays that contain runtime specific configuration
fn supported_layers_types() -> &'static [&'static str] {
&["application/vnd.bytecodealliance.wasm.component.layer.v0+wasm"]
}
Expand Down
2 changes: 1 addition & 1 deletion crates/containerd-shim-wasm/src/container/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod engine;
mod path;

pub(crate) use context::WasiContext;
pub use context::{RuntimeContext, WasiEntrypoint, WasiLoadingStrategy};
pub use context::{Entrypoint, RuntimeContext, Source};
pub use engine::Engine;
pub use instance::Instance;
pub use path::PathResolve;
Expand Down
11 changes: 6 additions & 5 deletions crates/containerd-shim-wasm/src/sys/unix/container/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use libcontainer::workload::{
use oci_spec::image::Platform;
use oci_spec::runtime::Spec;

use crate::container::{Engine, PathResolve, RuntimeContext, Stdio, WasiContext};
use crate::container::{Engine, PathResolve, RuntimeContext, Source, Stdio, WasiContext};
use crate::sandbox::oci::WasmLayer;

#[derive(Clone)]
Expand Down Expand Up @@ -88,10 +88,7 @@ impl<E: Engine> Executor<E> {

fn inner(&self, spec: &Spec) -> &InnerExecutor {
self.inner.get_or_init(|| {
// if the spec has oci annotations we know it is wasm so short circuit checks
if !self.wasm_layers.is_empty() {
InnerExecutor::Wasm
} else if is_linux_container(&self.ctx(spec)).is_ok() {
if is_linux_container(&self.ctx(spec)).is_ok() {
InnerExecutor::Linux
} else if self.engine.can_handle(&self.ctx(spec)).is_ok() {
InnerExecutor::Wasm
Expand All @@ -103,6 +100,10 @@ impl<E: Engine> Executor<E> {
}

fn is_linux_container(ctx: &impl RuntimeContext) -> Result<()> {
if let Source::Oci(_) = ctx.entrypoint().source {
bail!("the entry point contains wasm layers")
};

let executable = ctx
.entrypoint()
.arg0
Expand Down
20 changes: 9 additions & 11 deletions crates/containerd-shim-wasmedge/src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Result};
use containerd_shim_wasm::container::{
Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, WasiLoadingStrategy,
Engine, Entrypoint, Instance, PathResolve, RuntimeContext, Source, Stdio,
};
use log::debug;
use wasmedge_sdk::config::{ConfigBuilder, HostRegistrationConfigOptions};
Expand Down Expand Up @@ -35,10 +35,11 @@ impl Engine for WasmEdgeEngine {
fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result<i32> {
let args = ctx.args();
let envs: Vec<_> = std::env::vars().map(|(k, v)| format!("{k}={v}")).collect();
let WasiEntrypoint {
path: entrypoint_path,
let Entrypoint {
source,
func,
arg0: _,
name,
} = ctx.entrypoint();

let mut vm = self.vm.clone();
Expand All @@ -50,16 +51,13 @@ impl Engine for WasmEdgeEngine {
Some(vec!["/:/"]),
);

let mod_name = match entrypoint_path.file_stem() {
Some(name) => name.to_string_lossy().to_string(),
None => "main".to_string(),
};
let mod_name = name.unwrap_or_else(|| "main".to_string());

PluginManager::load(None)?;
let vm = vm.auto_detect_plugins()?;

let vm = match ctx.wasi_loading_strategy() {
WasiLoadingStrategy::File(path) => {
let vm = match source {
Source::File(path) => {
debug!("loading module from file {path:?}");
let path = path
.resolve_in_path_or_cwd()
Expand All @@ -69,12 +67,12 @@ impl Engine for WasmEdgeEngine {
vm.register_module_from_file(&mod_name, path)
.context("registering module")?
}
WasiLoadingStrategy::Oci([module]) => {
Source::Oci([module]) => {
log::info!("loading module from wasm OCI layers");
vm.register_module_from_bytes(&mod_name, &module.layer)
.context("registering module")?
}
WasiLoadingStrategy::Oci(_modules) => {
Source::Oci(_modules) => {
bail!("only a single module is supported when using images with OCI layers")
}
};
Expand Down
20 changes: 9 additions & 11 deletions crates/containerd-shim-wasmer/src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Result};
use containerd_shim_wasm::container::{
Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, WasiLoadingStrategy,
Engine, Entrypoint, Instance, PathResolve, RuntimeContext, Source, Stdio,
};
use wasmer::{Module, Store};
use wasmer_wasix::virtual_fs::host_fs::FileSystem;
Expand All @@ -21,22 +21,20 @@ impl Engine for WasmerEngine {
fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result<i32> {
let args = ctx.args();
let envs = std::env::vars();
let WasiEntrypoint {
path,
let Entrypoint {
source,
func,
arg0: _,
name,
} = ctx.entrypoint();

let mod_name = match path.file_stem() {
Some(name) => name.to_string_lossy().to_string(),
None => "main".to_string(),
};
let mod_name = name.unwrap_or_else(|| "main".to_string());

log::info!("Create a Store");
let mut store = Store::new(self.engine.clone());

let module = match ctx.wasi_loading_strategy() {
WasiLoadingStrategy::File(path) => {
let module = match source {
Source::File(path) => {
log::info!("loading module from file {path:?}");
let path = path
.resolve_in_path_or_cwd()
Expand All @@ -45,12 +43,12 @@ impl Engine for WasmerEngine {

Module::from_file(&store, path)?
}
WasiLoadingStrategy::Oci([module]) => {
Source::Oci([module]) => {
log::info!("loading module wasm OCI layers");
log::info!("loading module wasm OCI layers");
Module::from_binary(&store, &module.layer)?
}
WasiLoadingStrategy::Oci(_modules) => {
Source::Oci(_modules) => {
bail!("only a single module is supported when using images with OCI layers")
}
};
Expand Down
Loading

0 comments on commit 1f5d6f3

Please sign in to comment.