From 8878701acf98991dfcf837951a84e29252e2f761 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Thu, 16 Nov 2023 00:01:32 +0000 Subject: [PATCH 1/3] Improve the API for loading shim from file or OCI Signed-off-by: James Sturtevant --- .../src/container/context.rs | 110 +++++++++++++++--- .../src/container/engine.rs | 6 +- .../containerd-shim-wasm/src/container/mod.rs | 2 +- .../src/sandbox/containerd.rs | 21 +--- .../src/sys/unix/container/executor.rs | 1 + .../src/sys/unix/container/instance.rs | 2 +- .../containerd-shim-wasmedge/src/instance.rs | 21 ++-- crates/containerd-shim-wasmer/src/instance.rs | 19 ++- .../containerd-shim-wasmtime/src/instance.rs | 19 +-- 9 files changed, 139 insertions(+), 62 deletions(-) diff --git a/crates/containerd-shim-wasm/src/container/context.rs b/crates/containerd-shim-wasm/src/container/context.rs index 97fea259a..5eba36b16 100644 --- a/crates/containerd-shim-wasm/src/container/context.rs +++ b/crates/containerd-shim-wasm/src/container/context.rs @@ -10,10 +10,6 @@ pub trait RuntimeContext { // path to the entrypoint executable. fn args(&self) -> &[String]; - // ctx.entrypoint() returns the entrypoint path from arguments on the runtime - // spec process field. - fn entrypoint(&self) -> Option<&Path>; - // ctx.wasi_entrypoint() returns a `WasiEntrypoint` with the path to the module to use // as an entrypoint and the name of the exported function to call, obtained from the // arguments on process OCI spec. @@ -22,16 +18,22 @@ 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 wasi_entrypoint(&self) -> WasiEntrypoint; + fn entrypoint(&self) -> WasiEntrypoint; - fn wasm_layers(&self) -> &[WasmLayer]; + fn wasi_loading_strategy(&self) -> WasiLoadingStrategy; fn platform(&self) -> &Platform; } -pub struct WasiEntrypoint { +pub enum WasiLoadingStrategy<'a> { + File(PathBuf), + Oci(&'a [WasmLayer]), +} + +pub struct WasiEntrypoint<'a> { pub path: PathBuf, pub func: String, + pub arg0: Option<&'a Path>, } pub(crate) struct WasiContext<'a> { @@ -50,21 +52,26 @@ impl RuntimeContext for WasiContext<'_> { .unwrap_or_default() } - fn entrypoint(&self) -> Option<&Path> { - self.args().first().map(Path::new) - } + fn entrypoint(&self) -> WasiEntrypoint { + let arg0 = self.args().first(); - fn wasi_entrypoint(&self) -> WasiEntrypoint { - let arg0 = self.args().first().map(String::as_str).unwrap_or(""); - let (path, func) = arg0.split_once('#').unwrap_or((arg0, "_start")); + 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 wasm_layers(&self) -> &[WasmLayer] { - self.wasm_layers + fn wasi_loading_strategy(&self) -> WasiLoadingStrategy { + if self.wasm_layers.is_empty() { + WasiLoadingStrategy::File(self.entrypoint().path.clone()) + } else { + WasiLoadingStrategy::Oci(self.wasm_layers) + } } fn platform(&self) -> &Platform { @@ -75,6 +82,7 @@ impl RuntimeContext for WasiContext<'_> { #[cfg(test)] mod tests { use anyhow::Result; + use oci_spec::image::Descriptor; use oci_spec::runtime::{ProcessBuilder, RootBuilder, SpecBuilder}; use super::*; @@ -167,7 +175,7 @@ mod tests { platform: &Platform::default(), }; - let path = ctx.wasi_entrypoint().path; + let path = ctx.entrypoint().path; assert!(path.as_os_str().is_empty()); Ok(()) @@ -195,9 +203,10 @@ mod tests { platform: &Platform::default(), }; - let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); + let WasiEntrypoint { path, func, arg0 } = ctx.entrypoint(); assert_eq!(path, Path::new("hello.wat")); assert_eq!(func, "foo"); + assert_eq!(arg0, Some(Path::new("hello.wat#foo"))); Ok(()) } @@ -224,9 +233,74 @@ mod tests { platform: &Platform::default(), }; - let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); + let WasiEntrypoint { path, func, arg0 } = ctx.entrypoint(); assert_eq!(path, Path::new("/root/hello.wat")); assert_eq!(func, "_start"); + assert_eq!(arg0, Some(Path::new("/root/hello.wat"))); + + Ok(()) + } + + #[test] + fn test_loading_strategy_is_file_when_no_layers() -> Result<()> { + let spec = SpecBuilder::default() + .root(RootBuilder::default().path("rootfs").build()?) + .process( + ProcessBuilder::default() + .cwd("/") + .args(vec![ + "/root/hello.wat#foo".to_string(), + "echo".to_string(), + "hello".to_string(), + ]) + .build()?, + ) + .build()?; + + let ctx = WasiContext { + spec: &spec, + wasm_layers: &[], + platform: &Platform::default(), + }; + + let expected_path = PathBuf::from("/root/hello.wat"); + assert!(matches!( + ctx.wasi_loading_strategy(), + WasiLoadingStrategy::File(p) if p == expected_path + )); + + Ok(()) + } + + #[test] + fn test_loading_strategy_is_oci_when_layers_present() -> Result<()> { + let spec = SpecBuilder::default() + .root(RootBuilder::default().path("rootfs").build()?) + .process( + ProcessBuilder::default() + .cwd("/") + .args(vec![ + "/root/hello.wat".to_string(), + "echo".to_string(), + "hello".to_string(), + ]) + .build()?, + ) + .build()?; + + let ctx = WasiContext { + spec: &spec, + wasm_layers: &[WasmLayer { + layer: vec![], + config: Descriptor::new(oci_spec::image::MediaType::Other("".to_string()), 10, ""), + }], + platform: &Platform::default(), + }; + + assert!(matches!( + ctx.wasi_loading_strategy(), + WasiLoadingStrategy::Oci(_) + )); Ok(()) } diff --git a/crates/containerd-shim-wasm/src/container/engine.rs b/crates/containerd-shim-wasm/src/container/engine.rs index 8c9783117..28d0f08ab 100644 --- a/crates/containerd-shim-wasm/src/container/engine.rs +++ b/crates/containerd-shim-wasm/src/container/engine.rs @@ -20,7 +20,7 @@ pub trait Engine: Clone + Send + Sync + 'static { /// * a parsable `wat` file. fn can_handle(&self, ctx: &impl RuntimeContext) -> Result<()> { let path = ctx - .wasi_entrypoint() + .entrypoint() .path .resolve_in_path_or_cwd() .next() @@ -36,4 +36,8 @@ pub trait Engine: Clone + Send + Sync + 'static { Ok(()) } + + fn supported_layers_types() -> &'static [&'static str] { + &["application/vnd.bytecodealliance.wasm.component.layer.v0+wasm"] + } } diff --git a/crates/containerd-shim-wasm/src/container/mod.rs b/crates/containerd-shim-wasm/src/container/mod.rs index ff2aca7f5..8b8b9165f 100644 --- a/crates/containerd-shim-wasm/src/container/mod.rs +++ b/crates/containerd-shim-wasm/src/container/mod.rs @@ -15,7 +15,7 @@ mod engine; mod path; pub(crate) use context::WasiContext; -pub use context::{RuntimeContext, WasiEntrypoint}; +pub use context::{RuntimeContext, WasiEntrypoint, WasiLoadingStrategy}; pub use engine::Engine; pub use instance::Instance; pub use path::PathResolve; diff --git a/crates/containerd-shim-wasm/src/sandbox/containerd.rs b/crates/containerd-shim-wasm/src/sandbox/containerd.rs index 3168c1dd7..8a5d2782b 100644 --- a/crates/containerd-shim-wasm/src/sandbox/containerd.rs +++ b/crates/containerd-shim-wasm/src/sandbox/containerd.rs @@ -119,6 +119,7 @@ impl Client { pub fn load_modules( &self, containerd_id: impl ToString, + supported_layer_types: &[&str], ) -> Result<(Vec, Platform)> { let image_name = self.get_image(containerd_id.to_string())?; let digest = self.get_image_content_sha(image_name)?; @@ -141,7 +142,7 @@ impl Client { let layers = manifest .layers() .iter() - .filter(|x| !is_image_layer_type(x.media_type())) + .filter(|x| is_wasm_layer(x.media_type(), supported_layer_types)) .map(|config| { self.read_content(config.digest()).map(|module| WasmLayer { config: config.clone(), @@ -153,20 +154,6 @@ impl Client { } } -fn is_image_layer_type(media_type: &MediaType) -> bool { - match media_type { - MediaType::ImageLayer - | MediaType::ImageLayerGzip - | MediaType::ImageLayerNonDistributable - | MediaType::ImageLayerNonDistributableGzip - | MediaType::ImageLayerNonDistributableZstd - | MediaType::ImageLayerZstd => true, - MediaType::Other(s) - if s.as_str() - .starts_with("application/vnd.docker.image.rootfs.") => - { - true - } - _ => false, - } +fn is_wasm_layer(media_type: &MediaType, supported_layer_types: &[&str]) -> bool { + supported_layer_types.contains(&media_type.to_string().as_str()) } diff --git a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs index 287f74d3c..450a529e9 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs @@ -105,6 +105,7 @@ impl Executor { fn is_linux_container(ctx: &impl RuntimeContext) -> Result<()> { let executable = ctx .entrypoint() + .arg0 .context("no entrypoint provided")? .resolve_in_path() .find_map(|p| -> Option { diff --git a/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs b/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs index 55c6b111c..8b228066f 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs @@ -45,7 +45,7 @@ impl SandboxInstance for Instance { // check if container is OCI image with wasm layers and attempt to read the module let (modules, platform) = containerd::Client::connect(cfg.get_containerd_address(), &namespace)? - .load_modules(&id) + .load_modules(&id, E::supported_layers_types()) .unwrap_or_else(|e| { log::warn!("Error obtaining wasm layers for container {id}. Will attempt to use files inside container image. Error: {e}"); (vec![], Platform::default()) diff --git a/crates/containerd-shim-wasmedge/src/instance.rs b/crates/containerd-shim-wasmedge/src/instance.rs index ce723d439..5f5b09ebd 100644 --- a/crates/containerd-shim-wasmedge/src/instance.rs +++ b/crates/containerd-shim-wasmedge/src/instance.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use containerd_shim_wasm::container::{ - Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, + Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, WasiLoadingStrategy, }; use log::debug; use wasmedge_sdk::config::{ConfigBuilder, HostRegistrationConfigOptions}; @@ -38,7 +38,8 @@ impl Engine for WasmEdgeEngine { let WasiEntrypoint { path: entrypoint_path, func, - } = ctx.wasi_entrypoint(); + arg0: _, + } = ctx.entrypoint(); let mut vm = self.vm.clone(); vm.wasi_module_mut() @@ -57,10 +58,10 @@ impl Engine for WasmEdgeEngine { PluginManager::load(None)?; let vm = vm.auto_detect_plugins()?; - let vm = match ctx.wasm_layers() { - [] => { - debug!("loading module from file"); - let path = entrypoint_path + let vm = match ctx.wasi_loading_strategy() { + WasiLoadingStrategy::File(path) => { + debug!("loading module from file {path:?}"); + let path = path .resolve_in_path_or_cwd() .next() .context("module not found")?; @@ -68,17 +69,19 @@ impl Engine for WasmEdgeEngine { vm.register_module_from_file(&mod_name, path) .context("registering module")? } - [module] => { + WasiLoadingStrategy::Oci([module]) => { log::info!("loading module from wasm OCI layers"); vm.register_module_from_bytes(&mod_name, &module.layer) .context("registering module")? } - [..] => bail!("only a single module is supported when using images with OCI layers"), + WasiLoadingStrategy::Oci(_modules) => { + bail!("only a single module is supported when using images with OCI layers") + } }; stdio.redirect()?; - log::debug!("running {entrypoint_path:?} with method {func:?}"); + log::debug!("running with method {func:?}"); vm.run_func(Some(&mod_name), func, vec![])?; let status = vm diff --git a/crates/containerd-shim-wasmer/src/instance.rs b/crates/containerd-shim-wasmer/src/instance.rs index 7ca35a2ec..d5344da3b 100644 --- a/crates/containerd-shim-wasmer/src/instance.rs +++ b/crates/containerd-shim-wasmer/src/instance.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use containerd_shim_wasm::container::{ - Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, + Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, WasiLoadingStrategy, }; use wasmer::{Module, Store}; use wasmer_wasix::virtual_fs::host_fs::FileSystem; @@ -21,7 +21,11 @@ impl Engine for WasmerEngine { fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result { let args = ctx.args(); let envs = std::env::vars(); - let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); + let WasiEntrypoint { + path, + func, + arg0: _, + } = ctx.entrypoint(); let mod_name = match path.file_stem() { Some(name) => name.to_string_lossy().to_string(), @@ -31,8 +35,8 @@ impl Engine for WasmerEngine { log::info!("Create a Store"); let mut store = Store::new(self.engine.clone()); - let module = match ctx.wasm_layers() { - [] => { + let module = match ctx.wasi_loading_strategy() { + WasiLoadingStrategy::File(path) => { log::info!("loading module from file {path:?}"); let path = path .resolve_in_path_or_cwd() @@ -41,11 +45,14 @@ impl Engine for WasmerEngine { Module::from_file(&store, path)? } - [module] => { + WasiLoadingStrategy::Oci([module]) => { + log::info!("loading module wasm OCI layers"); log::info!("loading module wasm OCI layers"); Module::from_binary(&store, &module.layer)? } - [..] => bail!("only a single module is supported when using images with OCI layers"), + WasiLoadingStrategy::Oci(_modules) => { + bail!("only a single module is supported when using images with OCI layers") + } }; let runtime = tokio::runtime::Builder::new_multi_thread() diff --git a/crates/containerd-shim-wasmtime/src/instance.rs b/crates/containerd-shim-wasmtime/src/instance.rs index 3655a2773..22ff8bad2 100644 --- a/crates/containerd-shim-wasmtime/src/instance.rs +++ b/crates/containerd-shim-wasmtime/src/instance.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use containerd_shim_wasm::container::{ - Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiEntrypoint, + Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiLoadingStrategy, }; use wasi_common::I32Exit; use wasmtime::{Linker, Module, Store}; @@ -35,11 +35,9 @@ impl Engine for WasmtimeEngine { let wctx = wasi_builder.build(); log::info!("wasi context ready"); - let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); - - let module = match ctx.wasm_layers() { - [] => { - log::info!("loading module from file"); + let module = match ctx.wasi_loading_strategy() { + WasiLoadingStrategy::File(path) => { + log::info!("loading module from path {path:?}"); let path = path .resolve_in_path_or_cwd() .next() @@ -47,11 +45,13 @@ impl Engine for WasmtimeEngine { Module::from_file(&self.engine, path)? } - [module] => { + WasiLoadingStrategy::Oci([module]) => { log::info!("loading module wasm OCI layers"); Module::from_binary(&self.engine, &module.layer)? } - [..] => bail!("only a single module is supported when using images with OCI layers"), + WasiLoadingStrategy::Oci(_modules) => { + bail!("only a single module is supported when using images with OCI layers") + } }; let mut linker = Linker::new(&self.engine); @@ -63,11 +63,12 @@ impl Engine for WasmtimeEngine { let instance: wasmtime::Instance = linker.instantiate(&mut store, &module)?; log::info!("getting start function"); + let func = ctx.entrypoint().func; let start_func = instance .get_func(&mut store, &func) .context("module does not have a WASI start function")?; - log::debug!("running {path:?} with start function {func:?}"); + log::debug!("running with start function {func:?}"); let status = start_func.call(&mut store, &[], &mut []); let status = status.map(|_| 0).or_else(|err| { From 1f5d6f3305509ba058dc36a186e3def4c2feed53 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Fri, 17 Nov 2023 03:53:36 +0000 Subject: [PATCH 2/3] Move logic to entry point Signed-off-by: James Sturtevant --- .../src/container/context.rs | 94 +++++++++++++------ .../src/container/engine.rs | 20 +++- .../containerd-shim-wasm/src/container/mod.rs | 2 +- .../src/sys/unix/container/executor.rs | 11 ++- .../containerd-shim-wasmedge/src/instance.rs | 20 ++-- crates/containerd-shim-wasmer/src/instance.rs | 20 ++-- .../containerd-shim-wasmtime/src/instance.rs | 14 +-- 7 files changed, 113 insertions(+), 68 deletions(-) diff --git a/crates/containerd-shim-wasm/src/container/context.rs b/crates/containerd-shim-wasm/src/container/context.rs index 5eba36b16..69f0e472a 100644 --- a/crates/containerd-shim-wasm/src/container/context.rs +++ b/crates/containerd-shim-wasm/src/container/context.rs @@ -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, pub arg0: Option<&'a Path>, + pub source: Source<'a>, } pub(crate) struct WasiContext<'a> { @@ -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, } } @@ -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(()) } @@ -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(()) } @@ -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(()) } @@ -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(()) @@ -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(()) } diff --git a/crates/containerd-shim-wasm/src/container/engine.rs b/crates/containerd-shim-wasm/src/container/engine.rs index 28d0f08ab..77f10cc3f 100644 --- a/crates/containerd-shim-wasm/src/container/engine.rs +++ b/crates/containerd-shim-wasm/src/container/engine.rs @@ -3,6 +3,7 @@ use std::io::Read; use anyhow::{Context, Result}; +use super::Source; use crate::container::{PathResolve, RuntimeContext}; use crate::sandbox::Stdio; @@ -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")?; @@ -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"] } diff --git a/crates/containerd-shim-wasm/src/container/mod.rs b/crates/containerd-shim-wasm/src/container/mod.rs index 8b8b9165f..5554a47d3 100644 --- a/crates/containerd-shim-wasm/src/container/mod.rs +++ b/crates/containerd-shim-wasm/src/container/mod.rs @@ -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; diff --git a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs index 450a529e9..81e45933e 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs @@ -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)] @@ -88,10 +88,7 @@ impl Executor { 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 @@ -103,6 +100,10 @@ impl Executor { } 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 diff --git a/crates/containerd-shim-wasmedge/src/instance.rs b/crates/containerd-shim-wasmedge/src/instance.rs index 5f5b09ebd..e8c33151e 100644 --- a/crates/containerd-shim-wasmedge/src/instance.rs +++ b/crates/containerd-shim-wasmedge/src/instance.rs @@ -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}; @@ -35,10 +35,11 @@ impl Engine for WasmEdgeEngine { fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result { 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(); @@ -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() @@ -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") } }; diff --git a/crates/containerd-shim-wasmer/src/instance.rs b/crates/containerd-shim-wasmer/src/instance.rs index d5344da3b..7f1702cd0 100644 --- a/crates/containerd-shim-wasmer/src/instance.rs +++ b/crates/containerd-shim-wasmer/src/instance.rs @@ -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; @@ -21,22 +21,20 @@ impl Engine for WasmerEngine { fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result { 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() @@ -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") } }; diff --git a/crates/containerd-shim-wasmtime/src/instance.rs b/crates/containerd-shim-wasmtime/src/instance.rs index 22ff8bad2..ec05b7443 100644 --- a/crates/containerd-shim-wasmtime/src/instance.rs +++ b/crates/containerd-shim-wasmtime/src/instance.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use containerd_shim_wasm::container::{ - Engine, Instance, PathResolve, RuntimeContext, Stdio, WasiLoadingStrategy, + Engine, Instance, PathResolve, RuntimeContext, Source, Stdio, }; use wasi_common::I32Exit; use wasmtime::{Linker, Module, Store}; @@ -20,14 +20,14 @@ impl Engine for WasmtimeEngine { fn run_wasi(&self, ctx: &impl RuntimeContext, stdio: Stdio) -> Result { log::info!("setting up wasi"); - let path = Dir::from_std_file(std::fs::File::open("/")?); + let root_path = Dir::from_std_file(std::fs::File::open("/")?); let envs: Vec<_> = std::env::vars().collect(); let wasi_builder = WasiCtxBuilder::new() .args(ctx.args())? .envs(envs.as_slice())? .inherit_stdio() - .preopened_dir(path, "/")?; + .preopened_dir(root_path, "/")?; stdio.redirect()?; @@ -35,8 +35,8 @@ impl Engine for WasmtimeEngine { let wctx = wasi_builder.build(); log::info!("wasi context ready"); - let module = match ctx.wasi_loading_strategy() { - WasiLoadingStrategy::File(path) => { + let module = match ctx.entrypoint().source { + Source::File(path) => { log::info!("loading module from path {path:?}"); let path = path .resolve_in_path_or_cwd() @@ -45,11 +45,11 @@ impl Engine for WasmtimeEngine { Module::from_file(&self.engine, path)? } - WasiLoadingStrategy::Oci([module]) => { + Source::Oci([module]) => { log::info!("loading module wasm OCI layers"); Module::from_binary(&self.engine, &module.layer)? } - WasiLoadingStrategy::Oci(_modules) => { + Source::Oci(_modules) => { bail!("only a single module is supported when using images with OCI layers") } }; From 724440848ca6697b91120feb15fee291eac0ac09 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Mon, 27 Nov 2023 19:37:59 +0000 Subject: [PATCH 3/3] Respond to feedback Signed-off-by: James Sturtevant --- .../src/container/context.rs | 17 +++++++++++------ crates/containerd-shim-wasmtime/src/instance.rs | 12 +++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/containerd-shim-wasm/src/container/context.rs b/crates/containerd-shim-wasm/src/container/context.rs index 69f0e472a..ae6c5cc47 100644 --- a/crates/containerd-shim-wasm/src/container/context.rs +++ b/crates/containerd-shim-wasm/src/container/context.rs @@ -10,14 +10,19 @@ pub trait RuntimeContext { // path to the entrypoint executable. fn args(&self) -> &[String]; - // ctx.wasi_entrypoint() returns a `WasiEntrypoint` with the path to the module to use - // as an entrypoint and the name of the exported function to call, obtained from the + // ctx.entrypoint() returns a `Entrypoint` with the following fields obtained from the first argument in the OCI spec for entrypoint: + // - `arg0` - raw entrypoint from the OCI spec + // - `name` - provided as the file name of the module in the entrypoint without the extension + // - `func` - name of the exported function to call, obtained from the // arguments on process OCI spec. - // The girst argument in the spec is specified as `path#func` where `func` is optional + // - `Source` - either a `File(PathBuf)` or `Oci(WasmLayer)`. When a `File` source the `PathBuf`` is provided by entrypoint in OCI spec. + // If the image contains custom OCI Wasm layers, the source is provided as an array of `WasmLayer` structs. + // + // The first argument in the OCI spec for entrypoint is specified as `path#func` where `func` is optional // and defaults to _start, e.g.: - // "/app/app.wasm#entry" -> { path: "/app/app.wasm", func: "entry" } - // "my_module.wat" -> { path: "my_module.wat", func: "_start" } - // "#init" -> { path: "", func: "init" } + // "/app/app.wasm#entry" -> { source: File("/app/app.wasm"), func: "entry", name: "Some(app)", arg0: "/app/app.wasm#entry" } + // "my_module.wat" -> { source: File("my_module.wat"), func: "_start", name: "Some(my_module)", arg0: "my_module.wat" } + // "#init" -> { source: File(""), func: "init", name: None, arg0: "#init" } fn entrypoint(&self) -> Entrypoint; // the platform for the container using the struct defined on the OCI spec definition diff --git a/crates/containerd-shim-wasmtime/src/instance.rs b/crates/containerd-shim-wasmtime/src/instance.rs index ec05b7443..9e0bd9bc9 100644 --- a/crates/containerd-shim-wasmtime/src/instance.rs +++ b/crates/containerd-shim-wasmtime/src/instance.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use containerd_shim_wasm::container::{ - Engine, Instance, PathResolve, RuntimeContext, Source, Stdio, + Engine, Entrypoint, Instance, PathResolve, RuntimeContext, Source, Stdio, }; use wasi_common::I32Exit; use wasmtime::{Linker, Module, Store}; @@ -34,8 +34,15 @@ impl Engine for WasmtimeEngine { log::info!("building wasi context"); let wctx = wasi_builder.build(); + let Entrypoint { + source, + func, + arg0: _, + name: _, + } = ctx.entrypoint(); + log::info!("wasi context ready"); - let module = match ctx.entrypoint().source { + let module = match source { Source::File(path) => { log::info!("loading module from path {path:?}"); let path = path @@ -63,7 +70,6 @@ impl Engine for WasmtimeEngine { let instance: wasmtime::Instance = linker.instantiate(&mut store, &module)?; log::info!("getting start function"); - let func = ctx.entrypoint().func; let start_func = instance .get_func(&mut store, &func) .context("module does not have a WASI start function")?;