1
0
Fork 0
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:
Furisto 2021-12-22 22:48:50 +01:00
parent d6e563078e
commit 9dbcab5788
9 changed files with 180 additions and 82 deletions

View File

@ -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"] }

View File

@ -1 +0,0 @@

View File

@ -1,2 +0,0 @@
pub mod default;
pub mod wasmer;

View File

@ -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)
}

View File

@ -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;

View File

@ -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,

View File

@ -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"
}
}

View File

@ -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");
}
}

View File

@ -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"
}
}