mirror of
https://github.com/containers/youki
synced 2024-06-02 04:46:13 +02:00
Implement executor and default handler
This commit is contained in:
parent
d6e563078e
commit
9dbcab5788
|
@ -5,6 +5,10 @@ authors = ["youki team"]
|
|||
edition = "2021"
|
||||
description = "Library for container creation"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasm-wasmer = ["wasmer", "wasmer-wasi"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
caps = "0.5.3"
|
||||
|
@ -26,8 +30,8 @@ libcgroups = { version = "0.0.1", path = "../libcgroups" }
|
|||
libseccomp = { version = "0.0.1", path = "../libseccomp" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wasmer = "2.1.0"
|
||||
wasmer-wasi = "2.1.0"
|
||||
wasmer = { version = "2.1.0", optional = true }
|
||||
wasmer-wasi = { version = "2.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "12dcd858543db0e7bfb1ef053d1b748f2fda74ee", features = ["proptests"] }
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
pub mod default;
|
||||
pub mod wasmer;
|
|
@ -1,69 +0,0 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use oci_spec::runtime::Spec;
|
||||
use wasmer::{Instance, Module, Store};
|
||||
use wasmer_wasi::WasiState;
|
||||
|
||||
static EMPTY: Vec<String> = Vec::new();
|
||||
|
||||
pub fn exec(spec: &Spec) -> Result<()> {
|
||||
let process = spec.process().as_ref();
|
||||
|
||||
let args = process
|
||||
.and_then(|p| p.args().as_ref())
|
||||
.unwrap_or_else(|| &EMPTY);
|
||||
let env = process
|
||||
.and_then(|p| p.env().as_ref())
|
||||
.unwrap_or_else(|| &EMPTY)
|
||||
.into_iter()
|
||||
.filter_map(|e| {
|
||||
e.split_once("=")
|
||||
.filter(|kv| !kv.0.contains('\u{0}') && !kv.1.contains('\u{0}'))
|
||||
.map(|kv| (kv.0.trim(), kv.1.trim()))
|
||||
});
|
||||
|
||||
if args.len() == 0 {
|
||||
bail!("at least one process arg must be specified")
|
||||
}
|
||||
|
||||
if !args[0].ends_with(".wasm") {
|
||||
bail!("first argument must be a wasm module, but was {}", args[0])
|
||||
}
|
||||
|
||||
let mut wasm_env = WasiState::new("youki_wasm_app")
|
||||
.args(args.iter().skip(1))
|
||||
.envs(env)
|
||||
.finalize()?;
|
||||
|
||||
let store = Store::default();
|
||||
let module = Module::from_file(&store, "hello.wasm").context("could not load wasm module")?;
|
||||
|
||||
let imports = wasm_env
|
||||
.import_object(&module)
|
||||
.context("could not retrieve wasm imports")?;
|
||||
let instance =
|
||||
Instance::new(&module, &imports).context("wasm module could not be instantiated")?;
|
||||
|
||||
let start = instance
|
||||
.exports
|
||||
.get_function("_start")
|
||||
.context("could not retrieve wasm module main function")?;
|
||||
start
|
||||
.call(&[])
|
||||
.context("wasm module was not executed successfuly")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -3,7 +3,6 @@ pub mod apparmor;
|
|||
pub mod capabilities;
|
||||
pub mod config;
|
||||
pub mod container;
|
||||
pub mod exec;
|
||||
pub mod hooks;
|
||||
pub mod namespaces;
|
||||
pub mod notify_socket;
|
||||
|
@ -15,3 +14,4 @@ pub mod signal;
|
|||
pub mod syscall;
|
||||
pub mod tty;
|
||||
pub mod utils;
|
||||
pub mod workload;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::args::ContainerArgs;
|
||||
use crate::apparmor;
|
||||
use crate::syscall::Syscall;
|
||||
use crate::workload::Executor;
|
||||
use crate::{
|
||||
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs::RootFS,
|
||||
rootless::Rootless, seccomp, tty, utils,
|
||||
|
@ -413,16 +414,11 @@ pub fn container_init_process(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(args) = proc.args() {
|
||||
crate::exec::wasmer::exec(spec).context("failed to exec")?;
|
||||
//utils::do_exec(&args[0], args)?;
|
||||
if proc.args().is_some() {
|
||||
Executor::new().exec(spec)
|
||||
} else {
|
||||
bail!("on non-Windows, at least one process arg entry is required")
|
||||
}
|
||||
|
||||
// After do_exec is called, the process is replaced with the container
|
||||
// payload through execvp, so it should never reach here.
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
// Before 3.19 it was possible for an unprivileged user to enter an user namespace,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
use std::ffi::CString;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use nix::unistd;
|
||||
use oci_spec::runtime::Spec;
|
||||
|
||||
use super::{ExecHandler, EMPTY};
|
||||
|
||||
pub struct DefaultExecHandler {}
|
||||
|
||||
impl ExecHandler for DefaultExecHandler {
|
||||
fn exec(&self, spec: &Spec) -> Result<()> {
|
||||
log::debug!("Executing workload with default handler");
|
||||
let args = spec
|
||||
.process()
|
||||
.as_ref()
|
||||
.and_then(|p| p.args().as_ref())
|
||||
.unwrap_or_else(|| &EMPTY);
|
||||
|
||||
if args.len() == 0 {
|
||||
bail!("at least one process arg must be specified")
|
||||
}
|
||||
|
||||
let executable = args[0].as_str();
|
||||
let p = CString::new(executable.as_bytes())
|
||||
.with_context(|| format!("failed to convert path {:?} to cstring", executable))?;
|
||||
let a: Vec<CString> = args
|
||||
.iter()
|
||||
.map(|s| CString::new(s.as_bytes()).unwrap_or_default())
|
||||
.collect();
|
||||
unistd::execvp(&p, &a)?;
|
||||
|
||||
// After do_exec is called, the process is replaced with the container
|
||||
// payload through execvp, so it should never reach here.
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn can_handle(&self, _: &Spec) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"default"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
use anyhow::{Result, Context};
|
||||
use oci_spec::runtime::Spec;
|
||||
|
||||
use self::{default::DefaultExecHandler};
|
||||
#[cfg(feature = "wasm-wasmer")]
|
||||
use self::{wasmer::WasmerExecHandler};
|
||||
|
||||
pub mod default;
|
||||
#[cfg(feature = "wasm-wasmer")]
|
||||
pub mod wasmer;
|
||||
|
||||
static EMPTY: Vec<String> = Vec::new();
|
||||
|
||||
pub trait ExecHandler {
|
||||
/// The name of the handler
|
||||
fn name(&self) -> &str;
|
||||
/// Executes the workload
|
||||
fn exec(&self, spec: &Spec) -> Result<()>;
|
||||
/// Checks if the handler is able to handle the workload
|
||||
fn can_handle(&self, spec: &Spec) -> Result<bool>;
|
||||
}
|
||||
pub struct Executor {
|
||||
handlers: Vec<Box<dyn ExecHandler>>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub fn new() -> Self {
|
||||
let mut handlers: Vec<Box<dyn ExecHandler>> = Vec::new();
|
||||
#[cfg(feature = "wasm-wasmer")]
|
||||
handlers.push(Box::new(WasmerExecHandler{}));
|
||||
handlers.push(Box::new(DefaultExecHandler{}));
|
||||
|
||||
Self { handlers }
|
||||
}
|
||||
|
||||
pub fn exec(&self, spec: &Spec) -> Result<()> {
|
||||
for handler in &self.handlers {
|
||||
if handler.can_handle(spec).with_context(|| format!("handler {} failed on selection",handler.name() ))? {
|
||||
handler.exec(spec).with_context(||format!("handler {} failed on exec", handler.name()))?;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!("no suitable execution handler has been registered");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use oci_spec::runtime::Spec;
|
||||
use wasmer::{Instance, Module, Store};
|
||||
use wasmer_wasi::WasiState;
|
||||
|
||||
use super::{ExecHandler, EMPTY};
|
||||
|
||||
pub struct WasmerExecHandler {}
|
||||
|
||||
impl ExecHandler for WasmerExecHandler {
|
||||
fn exec(&self, spec: &Spec) -> Result<()> {
|
||||
log::debug!("Executing workload with wasmer handler");
|
||||
let process = spec.process().as_ref();
|
||||
|
||||
let args = process
|
||||
.and_then(|p| p.args().as_ref())
|
||||
.unwrap_or_else(|| &EMPTY);
|
||||
let env = process
|
||||
.and_then(|p| p.env().as_ref())
|
||||
.unwrap_or_else(|| &EMPTY)
|
||||
.into_iter()
|
||||
.filter_map(|e| {
|
||||
e.split_once("=")
|
||||
.filter(|kv| !kv.0.contains('\u{0}') && !kv.1.contains('\u{0}'))
|
||||
.map(|kv| (kv.0.trim(), kv.1.trim()))
|
||||
});
|
||||
|
||||
if args.len() == 0 {
|
||||
bail!("at least one process arg must be specified")
|
||||
}
|
||||
|
||||
if !args[0].ends_with(".wasm") {
|
||||
bail!("first argument must be a wasm module, but was {}", args[0])
|
||||
}
|
||||
|
||||
let mut wasm_env = WasiState::new("youki_wasm_app")
|
||||
.args(args.iter().skip(1))
|
||||
.envs(env)
|
||||
.finalize()?;
|
||||
|
||||
let store = Store::default();
|
||||
let module =
|
||||
Module::from_file(&store, "hello.wasm").context("could not load wasm module")?;
|
||||
|
||||
let imports = wasm_env
|
||||
.import_object(&module)
|
||||
.context("could not retrieve wasm imports")?;
|
||||
let instance =
|
||||
Instance::new(&module, &imports).context("wasm module could not be instantiated")?;
|
||||
|
||||
let start = instance
|
||||
.exports
|
||||
.get_function("_start")
|
||||
.context("could not retrieve wasm module main function")?;
|
||||
start
|
||||
.call(&[])
|
||||
.context("wasm module was not executed successfuly")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn can_handle(&self, 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(&self) -> &str {
|
||||
"wasmer"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue