Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LinuxContainerExecutor to Wasmtime and WasmEdge shims #156

Merged
merged 22 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
558732d
implements can_handle in wasmtime executor.
Mossaka Jun 21, 2023
8b49b0c
test: add a python container to wasmtime shim for testing
Mossaka Jul 11, 2023
bf71029
feat: add docker buildx to CI
Mossaka Jul 12, 2023
2827e2a
add --load to test/py-flask to fix the CI issue
Mossaka Jul 12, 2023
bde59f0
feat: removed py-flask image and use hello-world image from docker.io
Mossaka Jul 31, 2023
e24adc0
refactor: move reset_stdio method to wasitest module
Mossaka Jul 31, 2023
2c7cc73
Merge remote-tracking branch 'upstream/main' into issue64
Mossaka Aug 1, 2023
0f93233
fix: changed the hello-world image to nginx for testing in k8s
Mossaka Aug 1, 2023
6a86100
feat: implement can_handle for default executor
Mossaka Aug 1, 2023
01d4a64
refactor: restructure the files. adding a new executors mod
Mossaka Aug 1, 2023
11403c2
Merge remote-tracking branch 'upstream/main' into issue64
Mossaka Aug 1, 2023
eb9f6e9
fix: removed the extra lines in CI
Mossaka Aug 1, 2023
ef1b387
fix: fetch nginx from docker.io in k8s
Mossaka Aug 1, 2023
2f07ec5
fix: remove never pull policy from k8s
Mossaka Aug 1, 2023
70668d5
feat: implement check for shebang in the linux executor
Mossaka Aug 2, 2023
7116634
isolate test/k3s deploy.yaml file since wasmedge doesn't have default…
Mossaka Aug 2, 2023
f5cca54
Merge remote-tracking branch 'upstream/main' into issue64
Mossaka Aug 4, 2023
a5dd8be
simplify the logic in verifying ELF file and shebang and apply review…
Mossaka Aug 4, 2023
f46aa5f
simplify the can_handle logic in wasi executor
Mossaka Aug 4, 2023
3dee7bf
pass std i/o to default executor
Mossaka Aug 10, 2023
23ff239
Merge branch 'main' into issue64
Mossaka Aug 14, 2023
bd4f5b8
feat: add container executor to wasmedge shim
Mossaka Aug 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ bin/kind: test/k8s/Dockerfile
test/k8s/_out/img: test/k8s/Dockerfile Cargo.toml Cargo.lock $(shell find . -type f -name '*.rs')
mkdir -p $(@D) && $(DOCKER_BUILD) -f test/k8s/Dockerfile --iidfile=$(@) --load .

.PHONY: test/nginx
test/nginx:
docker pull docker.io/nginx:latest
mkdir -p $@/out && docker save -o $@/out/img.tar docker.io/nginx:latest

.PHONY: test/k8s/cluster
test/k8s/cluster: target/wasm32-wasi/$(TARGET)/img.tar bin/kind test/k8s/_out/img bin/kind
bin/kind create cluster --name $(KIND_CLUSTER_NAME) --image="$(shell cat test/k8s/_out/img)" && \
Expand Down
3 changes: 3 additions & 0 deletions crates/containerd-shim-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
pub mod sandbox;

pub mod services;

#[cfg(feature = "libcontainer")]
pub mod libcontainer_instance;
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use crate::sandbox::oci;
use libcontainer::workload::default::DefaultExecutor;
use libcontainer::workload::{Executor, ExecutorError};
use nix::unistd::{dup, dup2};

use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use oci_spec::runtime::Spec;
use std::io::Read;
use std::{fs::OpenOptions, os::fd::RawFd, path::PathBuf};

#[derive(Default)]
pub struct LinuxContainerExecutor {
stdin: Option<RawFd>,
stdout: Option<RawFd>,
stderr: Option<RawFd>,
default_executor: DefaultExecutor,
}

impl LinuxContainerExecutor {
pub fn new(stdin: Option<RawFd>, stdout: Option<RawFd>, stderr: Option<RawFd>) -> Self {
Self {
stdin,
stdout,
stderr,
..Default::default()
}
}
}

impl Executor for LinuxContainerExecutor {
fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {
redirect_io(self.stdin, self.stdout, self.stderr).map_err(|err| {
log::error!("failed to redirect io: {}", err);
ExecutorError::Other(format!("failed to redirect io: {}", err))
})?;
self.default_executor.exec(spec)
}

fn can_handle(&self, spec: &Spec) -> bool {
let args = oci::get_args(spec);

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
use std::os::unix::fs::PermissionsExt;
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
}
}
}

fn name(&self) -> &'static str {
self.default_executor.name()
}
}

fn redirect_io(stdin: Option<i32>, stdout: Option<i32>, stderr: Option<i32>) -> anyhow::Result<()> {
if let Some(stdin) = stdin {
dup(STDIN_FILENO)?;
dup2(stdin, STDIN_FILENO)?;
}
if let Some(stdout) = stdout {
dup(STDOUT_FILENO)?;
dup2(stdout, STDOUT_FILENO)?;
}
if let Some(stderr) = stderr {
dup(STDERR_FILENO)?;
dup2(stderr, STDERR_FILENO)?;
}
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::sandbox::{
use crate::sandbox::InstanceConfig;
use std::{path::PathBuf, thread};

use super::{error::Error, instance::Wait, Instance};
use crate::sandbox::{error::Error, instance::Wait, Instance};

/// LibcontainerInstance is a trait that gets implemented by a WASI runtime that
/// uses youki's libcontainer library as the container runtime.
Expand Down
5 changes: 5 additions & 0 deletions crates/containerd-shim-wasm/src/libcontainer_instance/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod container_executor;
pub mod instance;

pub use container_executor::LinuxContainerExecutor;
pub use instance::LibcontainerInstance;
6 changes: 0 additions & 6 deletions crates/containerd-shim-wasm/src/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@

use crate::services::sandbox;

// pub mod cgroups;
pub mod error;
// pub mod exec;
pub mod instance;
pub mod instance_utils;
#[cfg(feature = "libcontainer")]
pub mod libcontainer_instance;
pub mod manager;
pub mod shim;
#[cfg(feature = "libcontainer")]
pub use libcontainer_instance::LibcontainerInstance;

pub use error::{Error, Result};
pub use instance::{EngineGetter, Instance, InstanceConfig};
Expand Down
35 changes: 29 additions & 6 deletions crates/containerd-shim-wasmedge/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use oci_spec::runtime::Spec;
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use libcontainer::workload::{Executor, ExecutorError};
use log::debug;
use std::os::unix::io::RawFd;
use std::{os::unix::io::RawFd, path::PathBuf};

use wasmedge_sdk::{
config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions},
Expand All @@ -16,9 +16,19 @@ use wasmedge_sdk::{
const EXECUTOR_NAME: &str = "wasmedge";

pub struct WasmEdgeExecutor {
pub stdin: Option<RawFd>,
pub stdout: Option<RawFd>,
pub stderr: Option<RawFd>,
stdin: Option<RawFd>,
stdout: Option<RawFd>,
stderr: Option<RawFd>,
}

impl WasmEdgeExecutor {
pub fn new(stdin: Option<RawFd>, stdout: Option<RawFd>, stderr: Option<RawFd>) -> Self {
Self {
stdin,
stdout,
stderr,
}
}
}

impl Executor for WasmEdgeExecutor {
Expand All @@ -43,8 +53,21 @@ impl Executor for WasmEdgeExecutor {
};
}

fn can_handle(&self, _spec: &Spec) -> bool {
true
fn can_handle(&self, spec: &Spec) -> bool {
// check if the entrypoint of the spec is a wasm binary.
let args = oci::get_args(spec);
if args.is_empty() {
return false;
}

let start = args[0].clone();
let mut iterator = start.split('#');
let cmd = iterator.next().unwrap().to_string();
let path = PathBuf::from(cmd);

path.extension()
.map(|ext| ext.to_ascii_lowercase())
.is_some_and(|ext| ext == "wasm" || ext == "wat")
}

fn name(&self) -> &'static str {
Expand Down
13 changes: 7 additions & 6 deletions crates/containerd-shim-wasmedge/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ use std::sync::{Arc, Condvar, Mutex};

use anyhow::Context;
use anyhow::Result;
use containerd_shim_wasm::libcontainer_instance::LibcontainerInstance;
use containerd_shim_wasm::libcontainer_instance::LinuxContainerExecutor;
use containerd_shim_wasm::sandbox::error::Error;
use containerd_shim_wasm::sandbox::instance::ExitCode;
use containerd_shim_wasm::sandbox::instance_utils::maybe_open_stdio;
use containerd_shim_wasm::sandbox::{EngineGetter, InstanceConfig, LibcontainerInstance};
use containerd_shim_wasm::sandbox::{EngineGetter, InstanceConfig};
use libc::{dup2, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use nix::unistd::close;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -125,12 +127,11 @@ impl LibcontainerInstance for Wasi {

let syscall = create_syscall();
let err_others = |err| Error::Others(format!("failed to create container: {}", err));
let default_executor = Box::new(LinuxContainerExecutor::new(stdin, stdout, stderr));
let wasmedge_executor = Box::new(WasmEdgeExecutor::new(stdin, stdout, stderr));

let container = ContainerBuilder::new(self.id.clone(), syscall.as_ref())
.with_executor(vec![Box::new(WasmEdgeExecutor {
stdin,
stdout,
stderr,
})])
.with_executor(vec![default_executor, wasmedge_executor])
.map_err(err_others)?
.with_root_path(self.rootdir.clone())
.map_err(err_others)?
Expand Down
49 changes: 42 additions & 7 deletions crates/containerd-shim-wasmtime/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use nix::unistd::{dup, dup2};
use std::{fs::OpenOptions, os::fd::RawFd};
use std::{fs::OpenOptions, os::fd::RawFd, path::PathBuf};

use anyhow::{anyhow, Result};
use containerd_shim_wasm::sandbox::oci;
Expand All @@ -15,10 +15,26 @@ use crate::oci_wasmtime::{self, wasi_dir};
const EXECUTOR_NAME: &str = "wasmtime";

pub struct WasmtimeExecutor {
pub stdin: Option<RawFd>,
pub stdout: Option<RawFd>,
pub stderr: Option<RawFd>,
pub engine: Engine,
stdin: Option<RawFd>,
stdout: Option<RawFd>,
stderr: Option<RawFd>,
engine: Engine,
}

impl WasmtimeExecutor {
pub fn new(
stdin: Option<RawFd>,
stdout: Option<RawFd>,
stderr: Option<RawFd>,
engine: Engine,
) -> Self {
Self {
stdin,
stdout,
stderr,
engine,
}
}
}

impl Executor for WasmtimeExecutor {
Expand All @@ -39,8 +55,27 @@ impl Executor for WasmtimeExecutor {
};
}

fn can_handle(&self, _spec: &Spec) -> bool {
true
fn can_handle(&self, spec: &Spec) -> bool {
// check if the entrypoint of the spec is a wasm binary.
let args = oci::get_args(spec);
if args.is_empty() {
return false;
}

let start = args[0].clone();
let mut iterator = start.split('#');
let cmd = iterator.next().unwrap().to_string();
let path = PathBuf::from(cmd);

// TODO: do we need to validate the wasm binary?
Mossaka marked this conversation as resolved.
Show resolved Hide resolved
// ```rust
// let bytes = std::fs::read(path).unwrap();
// wasmparser::validate(&bytes).is_ok()
// ```

path.extension()
.map(|ext| ext.to_ascii_lowercase())
.is_some_and(|ext| ext == "wasm" || ext == "wat")
}

fn name(&self) -> &'static str {
Expand Down
Loading