-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add simplified libcontainer instance api
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
- Loading branch information
Showing
14 changed files
with
487 additions
and
567 deletions.
There are no files selected for viewing
40 changes: 40 additions & 0 deletions
40
crates/containerd-shim-wasm/src/libcontainer_instance/easy/context.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
use oci_spec::runtime::Spec; | ||
|
||
use crate::sandbox::oci; | ||
|
||
use super::stdio::Stdio; | ||
|
||
pub struct RuntimeContext { | ||
pub module: Option<String>, | ||
pub method: String, | ||
pub args: Vec<String>, | ||
pub envs: Vec<(String, String)>, | ||
pub stdio: Stdio, | ||
} | ||
|
||
impl From<&Spec> for RuntimeContext { | ||
fn from(spec: &Spec) -> Self { | ||
let (module, method) = oci::get_module(spec); | ||
|
||
Self { | ||
module, | ||
method, | ||
args: oci::get_args(spec).to_vec(), | ||
envs: envs_from_spec(spec), | ||
stdio: Stdio::default(), | ||
} | ||
} | ||
} | ||
|
||
fn envs_from_spec(spec: &Spec) -> Vec<(String, String)> { | ||
spec.process() | ||
.as_ref() | ||
.and_then(|p| p.env().clone()) | ||
.unwrap_or_default() | ||
.into_iter() | ||
.map(|v| match v.split_once('=') { | ||
None => (v, "".to_string()), | ||
Some((k, v)) => (k.to_string(), v.to_string()), | ||
}) | ||
.collect() | ||
} |
123 changes: 123 additions & 0 deletions
123
crates/containerd-shim-wasm/src/libcontainer_instance/easy/engine.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use std::{ | ||
fs::OpenOptions, | ||
io::Read, | ||
os::unix::prelude::PermissionsExt, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use libcontainer::workload::Executor; | ||
use oci_spec::runtime::Spec; | ||
|
||
use super::context::RuntimeContext; | ||
|
||
pub trait Engine: Send + Sync + 'static { | ||
fn name() -> &'static str; | ||
fn run(&self, ctx: RuntimeContext, spec: &Spec) -> anyhow::Result<i32>; | ||
fn can_handle(&self, ctx: RuntimeContext, _spec: &Spec) -> bool { | ||
is_wasm_binary(ctx.module) | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct LinuxContainerEngine(libcontainer::workload::default::DefaultExecutor); | ||
|
||
impl Engine for LinuxContainerEngine { | ||
fn name() -> &'static str { | ||
libcontainer::workload::default::DefaultExecutor::default().name() | ||
} | ||
|
||
fn can_handle(&self, ctx: RuntimeContext, _spec: &Spec) -> bool { | ||
is_linux_executable(&ctx.args) | ||
} | ||
|
||
fn run(&self, ctx: RuntimeContext, spec: &Spec) -> anyhow::Result<i32> { | ||
ctx.stdio.redirect().unwrap(); | ||
self.0.exec(spec)?; | ||
Ok(0) | ||
} | ||
} | ||
|
||
fn is_wasm_binary(module: Option<String>) -> bool { | ||
// check if the entrypoint of the spec is a wasm binary. | ||
if let Some(module) = module { | ||
Path::new(&module) | ||
.extension() | ||
.map(std::ffi::OsStr::to_ascii_lowercase) | ||
.is_some_and(|ext| ext == "wasm" || ext == "wat") | ||
} else { | ||
log::info!("WasmEdge cannot handle this workload, no arguments provided"); | ||
false | ||
} | ||
} | ||
|
||
fn is_linux_executable(args: &[String]) -> bool { | ||
if args.is_empty() { | ||
return false; | ||
} | ||
|
||
let executable = args[0].as_str(); | ||
|
||
// mostly follows youki's verify_binary implementation | ||
// https://github.com/containers/youki/blob/2d6fd7650bb0f22a78fb5fa982b5628f61fe25af/crates/libcontainer/src/process/container_init_process.rs#L106 | ||
let path = if executable.contains('/') { | ||
PathBuf::from(executable) | ||
} else { | ||
let path = std::env::var("PATH").unwrap_or_default(); | ||
// check each path in $PATH | ||
let mut found = false; | ||
let mut found_path = PathBuf::default(); | ||
for p in path.split(':') { | ||
let path = PathBuf::from(p).join(executable); | ||
if path.exists() { | ||
found = true; | ||
found_path = path; | ||
break; | ||
} | ||
} | ||
if !found { | ||
return false; | ||
} | ||
found_path | ||
}; | ||
|
||
// check execute permission | ||
let metadata = path.metadata(); | ||
if metadata.is_err() { | ||
log::info!("failed to get metadata of {:?}", path); | ||
return false; | ||
} | ||
let metadata = metadata.unwrap(); | ||
let permissions = metadata.permissions(); | ||
if !metadata.is_file() || permissions.mode() & 0o001 == 0 { | ||
log::info!("{} is not a file or has no execute permission", executable); | ||
return false; | ||
} | ||
|
||
// check the shebang and ELF magic number | ||
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header | ||
let mut buffer = [0; 4]; | ||
|
||
let file = OpenOptions::new().read(true).open(path); | ||
if file.is_err() { | ||
log::info!("failed to open {}", executable); | ||
return false; | ||
} | ||
let mut file = file.unwrap(); | ||
match file.read_exact(&mut buffer) { | ||
Ok(_) => {} | ||
Err(err) => { | ||
log::info!("failed to read shebang of {}: {}", executable, err); | ||
return false; | ||
} | ||
} | ||
match buffer { | ||
// ELF magic number | ||
[0x7f, 0x45, 0x4c, 0x46] => true, | ||
// shebang | ||
[0x23, 0x21, ..] => true, | ||
_ => { | ||
log::info!("{} is not a valid script or elf file", executable); | ||
false | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
crates/containerd-shim-wasm/src/libcontainer_instance/easy/executor.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
use std::sync::Arc; | ||
|
||
use libcontainer::workload::ExecutorError; | ||
|
||
use oci_spec::runtime::Spec; | ||
|
||
use crate::libcontainer_instance::easy::context::RuntimeContext; | ||
|
||
use super::{engine::Engine, stdio::Stdio}; | ||
|
||
pub struct Executor<E: Engine> { | ||
pub engine: Arc<E>, | ||
pub stdio: Stdio, | ||
} | ||
|
||
impl<E: Engine> libcontainer::workload::Executor for Executor<E> { | ||
fn can_handle(&self, spec: &Spec) -> bool { | ||
self.engine.can_handle(spec.into(), spec) | ||
} | ||
|
||
fn name(&self) -> &'static str { | ||
E::name() | ||
} | ||
|
||
fn exec(&self, spec: &oci_spec::runtime::Spec) -> Result<(), ExecutorError> { | ||
log::info!("calling start function"); | ||
let ctx = RuntimeContext { | ||
stdio: self.stdio.clone(), | ||
..spec.into() | ||
}; | ||
match self.engine.run(ctx, spec) { | ||
Ok(code) => std::process::exit(code), | ||
Err(err) => { | ||
log::info!("error running start function: {err}"); | ||
std::process::exit(137) | ||
} | ||
}; | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
crates/containerd-shim-wasm/src/libcontainer_instance/easy/instance.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use std::{ | ||
fs::File, | ||
io::ErrorKind, | ||
path::{Path, PathBuf}, | ||
sync::Arc, | ||
}; | ||
|
||
use libcontainer::{container::builder::ContainerBuilder, syscall::syscall::create_syscall}; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::sandbox::{self, instance::ExitCode, InstanceConfig}; | ||
|
||
use super::{ | ||
super::LibcontainerInstance, | ||
engine::{Engine, LinuxContainerEngine}, | ||
executor::Executor, | ||
stdio::Stdio, | ||
}; | ||
|
||
static DEFAULT_CONTAINER_ROOT_DIR: &str = "/run/containerd"; | ||
|
||
pub struct Instance<E: Engine> { | ||
engine: Arc<E>, | ||
exit_code: ExitCode, | ||
stdio: Stdio, | ||
bundle: PathBuf, | ||
rootdir: PathBuf, | ||
id: String, | ||
} | ||
|
||
impl<E: Engine> LibcontainerInstance for Instance<E> { | ||
type Engine = Arc<E>; | ||
|
||
fn new_libcontainer(id: String, cfg: Option<&InstanceConfig<Self::Engine>>) -> Self { | ||
let cfg = cfg.unwrap(); | ||
let engine = cfg.get_engine(); | ||
let bundle = cfg.get_bundle().unwrap_or_default().into(); | ||
let rootdir = determine_rootdir(E::name(), &bundle, cfg.get_namespace()).unwrap(); | ||
let stdio = Stdio { | ||
stdin: cfg.get_stdin().try_into().expect("failed to open stdin"), | ||
stdout: cfg.get_stdout().try_into().expect("failed to open stdout"), | ||
stderr: cfg.get_stderr().try_into().expect("failed to open stderr"), | ||
}; | ||
Self { | ||
id, | ||
exit_code: ExitCode::default(), | ||
engine, | ||
stdio, | ||
bundle, | ||
rootdir, | ||
} | ||
} | ||
|
||
fn get_id(&self) -> String { | ||
self.id.clone() | ||
} | ||
|
||
fn get_root_dir(&self) -> sandbox::Result<PathBuf> { | ||
Ok(self.rootdir.clone()) | ||
} | ||
|
||
fn get_exit_code(&self) -> ExitCode { | ||
self.exit_code.clone() | ||
} | ||
|
||
fn build_container(&self) -> Result<libcontainer::container::Container, sandbox::Error> { | ||
let syscall = create_syscall(); | ||
|
||
let err_others = | ||
|err| sandbox::Error::Others(format!("failed to create container: {}", err)); | ||
|
||
let wasm_executor = Box::new(Executor { | ||
engine: self.engine.clone(), | ||
stdio: self.stdio.clone(), | ||
}); | ||
|
||
let linux_executor: Box<Executor<LinuxContainerEngine>> = Box::new(Executor { | ||
engine: Arc::new(LinuxContainerEngine::default()), | ||
stdio: self.stdio.clone(), | ||
}); | ||
|
||
let container = ContainerBuilder::new(self.id.clone(), syscall.as_ref()) | ||
.with_executor(vec![linux_executor, wasm_executor]) | ||
.map_err(err_others)? | ||
.with_root_path(self.rootdir.clone()) | ||
.map_err(err_others)? | ||
.as_init(&self.bundle) | ||
.with_systemd(false) | ||
.build() | ||
.map_err(err_others)?; | ||
|
||
Ok(container) | ||
} | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
struct Options { | ||
root: Option<PathBuf>, | ||
} | ||
|
||
fn determine_rootdir<P: AsRef<Path>>( | ||
name: &str, | ||
bundle: P, | ||
namespace: String, | ||
) -> std::io::Result<PathBuf> { | ||
let default_root_dir = Path::new(DEFAULT_CONTAINER_ROOT_DIR).join(name); | ||
|
||
let file = match File::open(bundle.as_ref().join("options.json")) { | ||
Ok(f) => f, | ||
Err(err) if err.kind() == ErrorKind::NotFound => { | ||
return Ok(default_root_dir.join(namespace)); | ||
} | ||
Err(err) => return Err(err), | ||
}; | ||
|
||
let path = serde_json::from_reader::<_, Options>(file)? | ||
.root | ||
.unwrap_or(default_root_dir) | ||
.join(namespace); | ||
|
||
Ok(path) | ||
} |
5 changes: 5 additions & 0 deletions
5
crates/containerd-shim-wasm/src/libcontainer_instance/easy/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pub mod context; | ||
pub mod engine; | ||
pub mod executor; | ||
pub mod instance; | ||
pub mod stdio; |
Oops, something went wrong.