Skip to content

Commit 4304a70

Browse files
authored
feat: Make drivers more flexible (#51)
* feat: Wasm drivers can now spawn child processes and can access all files on the system using mounts * feat: Add support for reading stdout and stderr of child processes * fix: Formatting
1 parent 5cd38d7 commit 4304a70

15 files changed

Lines changed: 449 additions & 120 deletions

File tree

cli/build.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ fn get_version_info() -> Result<(u16, u16, u16, String), Box<dyn std::error::Err
5555
} else {
5656
"Stable".to_string()
5757
};
58-
Ok((version_numbers[0], version_numbers[1], version_numbers[2], stage))
58+
Ok((
59+
version_numbers[0],
60+
version_numbers[1],
61+
version_numbers[2],
62+
stage,
63+
))
5964
} else {
6065
Err("Version must have three parts".into())
6166
}

clients/wrapper/build.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ fn get_version_info() -> Result<(u16, u16, u16, String), Box<dyn std::error::Err
5555
} else {
5656
"Stable".to_string()
5757
};
58-
Ok((version_numbers[0], version_numbers[1], version_numbers[2], stage))
58+
Ok((
59+
version_numbers[0],
60+
version_numbers[1],
61+
version_numbers[2],
62+
stage,
63+
))
5964
} else {
6065
Err("Version must have three parts".into())
6166
}

controller/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ uuid = { version = "1.11.1", features = ["v4"] }
2222
# Command line arguments
2323
clap = { version = "4.5.26", features = ["derive"] }
2424

25+
# Regex parsing
26+
regex = "1.11.1"
27+
2528
# Configuration
2629
serde = { version = "1.0.217", features = ["derive"] }
2730
toml = "0.8.19"

controller/build.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ fn get_version_info() -> Result<(u16, u16, u16, String), Box<dyn std::error::Err
5555
} else {
5656
"Stable".to_string()
5757
};
58-
Ok((version_numbers[0], version_numbers[1], version_numbers[2], stage))
58+
Ok((
59+
version_numbers[0],
60+
version_numbers[1],
61+
version_numbers[2],
62+
stage,
63+
))
5964
} else {
6065
Err("Version must have three parts".into())
6166
}

controller/src/application/driver/wasm.rs

Lines changed: 102 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
use std::collections::HashMap;
12
use std::fs;
2-
use std::sync::{Arc, Mutex, Weak};
3+
use std::sync::{Arc, Mutex, RwLock, Weak};
34

45
use anyhow::{anyhow, Result};
5-
use cloudlet::driver;
6-
use cloudlet::driver::http::{Header, Method, Response};
7-
use cloudlet::driver::log::Level;
8-
use cloudlet_impl::WasmCloudlet;
9-
use exports::cloudlet::driver::bridge;
10-
use simplelog::{debug, error, info, warn};
11-
use tonic::async_trait;
12-
use wasmtime::component::{bindgen, Component, Linker, ResourceAny};
6+
use cloudlet::WasmCloudlet;
7+
use common::config::LoadFromTomlFile;
8+
use config::WasmConfig;
9+
use generated::exports::cloudlet::driver::bridge;
10+
use generated::Driver;
11+
use simplelog::{error, info, warn};
12+
use wasmtime::component::{Component, Linker, ResourceAny};
1313
use wasmtime::{Config, Engine, Store};
1414
use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
1515

@@ -18,23 +18,45 @@ use super::{DriverCloudletHandle, GenericDriver, Information};
1818
use crate::application::cloudlet::{Capabilities, Cloudlet, HostAndPort, RemoteController};
1919
use crate::storage::Storage;
2020

21-
mod cloudlet_impl;
21+
mod config;
2222

23-
bindgen!({
24-
world: "driver",
25-
path: "../protocol/wit/",
26-
});
23+
mod cloudlet;
24+
mod http;
25+
mod log;
26+
mod process;
27+
28+
pub mod generated {
29+
use wasmtime::component::bindgen;
30+
31+
bindgen!({
32+
world: "driver",
33+
path: "../protocol/wit/",
34+
});
35+
}
2736

2837
const WASM_DIRECTORY: &str = "wasm";
2938

30-
/* Caching of compiled wasm artifacts */
31-
const CACHE_CONFIG_FILE: &str = "wasm.toml";
32-
const DEFAULT_CACHE_CONFIG: &str = r#"# Comment out certain settings to use default values.
33-
# For more settings, please refer to the documentation:
39+
/* Caching of compiled wasm artifacts and other configuration */
40+
const CONFIG_FILE: &str = "wasm.toml";
41+
const DEFAULT_CONFIG: &str = r#"# For more settings, please refer to the documentation:
3442
# https://bytecodealliance.github.io/wasmtime/cli-cache.html
3543
3644
[cache]
37-
enabled = true"#;
45+
enabled = true
46+
47+
# This section is crucial for granting the drivers their required permissions
48+
# https://httprafa.github.io/atomic-cloud/controller/drivers/wasm/permissions/
49+
50+
[[drivers]]
51+
name = "pterodactyl"
52+
inherit_stdio = false
53+
inherit_args = false
54+
inherit_env = false
55+
inherit_network = true
56+
allow_ip_name_lookup = true
57+
allow_http = true
58+
allow_process = false
59+
mounts = []"#;
3860

3961
struct WasmDriverState {
4062
handle: Weak<WasmDriver>,
@@ -51,90 +73,12 @@ impl WasiView for WasmDriverState {
5173
}
5274
}
5375

54-
#[async_trait]
55-
impl driver::api::Host for WasmDriverState {
76+
impl generated::cloudlet::driver::api::Host for WasmDriverState {
5677
fn get_name(&mut self) -> String {
5778
self.handle.upgrade().unwrap().name.clone()
5879
}
5980
}
6081

61-
#[async_trait]
62-
impl driver::log::Host for WasmDriverState {
63-
fn log_string(&mut self, level: Level, message: String) {
64-
match level {
65-
Level::Info => info!(
66-
"<blue>[{}]</> {}",
67-
&self.handle.upgrade().unwrap().name.to_uppercase(),
68-
message
69-
),
70-
Level::Warn => warn!(
71-
"<blue>[{}]</> {}",
72-
&self.handle.upgrade().unwrap().name.to_uppercase(),
73-
message
74-
),
75-
Level::Error => error!(
76-
"<blue>[{}] {}",
77-
&self.handle.upgrade().unwrap().name.to_uppercase(),
78-
message
79-
),
80-
Level::Debug => debug!(
81-
"[{}] {}",
82-
&self.handle.upgrade().unwrap().name.to_uppercase(),
83-
message
84-
),
85-
}
86-
}
87-
}
88-
89-
#[async_trait]
90-
impl driver::http::Host for WasmDriverState {
91-
fn send_http_request(
92-
&mut self,
93-
method: Method,
94-
url: String,
95-
headers: Vec<Header>,
96-
body: Option<Vec<u8>>,
97-
) -> Option<Response> {
98-
let driver = self.handle.upgrade().unwrap();
99-
let mut request = match method {
100-
Method::Get => minreq::get(url),
101-
Method::Patch => minreq::patch(url),
102-
Method::Post => minreq::post(url),
103-
Method::Put => minreq::put(url),
104-
Method::Delete => minreq::delete(url),
105-
};
106-
if let Some(body) = body {
107-
request = request.with_body(body);
108-
}
109-
for header in headers {
110-
request = request.with_header(&header.key, &header.value);
111-
}
112-
let response = match request.send() {
113-
Ok(response) => response,
114-
Err(error) => {
115-
warn!(
116-
"<red>Failed</> to send HTTP request for driver <blue>{}</>: <red>{}</>",
117-
&driver.name, error
118-
);
119-
return None;
120-
}
121-
};
122-
Some(Response {
123-
status_code: response.status_code as u32,
124-
reason_phrase: response.reason_phrase.clone(),
125-
headers: response
126-
.headers
127-
.iter()
128-
.map(|header| Header {
129-
key: header.0.clone(),
130-
value: header.1.clone(),
131-
})
132-
.collect(),
133-
bytes: response.into_bytes(),
134-
})
135-
}
136-
}
137-
13882
struct WasmDriverHandle {
13983
store: Store<WasmDriverState>,
14084
resource: ResourceAny, // This is delete when the store is dropped
@@ -150,12 +94,18 @@ impl WasmDriverHandle {
15094
}
15195
}
15296

97+
pub struct WasmDriverData {
98+
child_processes: RwLock<HashMap<u32, std::process::Child>>,
99+
}
100+
153101
pub struct WasmDriver {
154102
own: Weak<WasmDriver>,
155103

156104
name: String,
157105
bindings: Driver,
158106
handle: Mutex<Option<WasmDriverHandle>>,
107+
108+
data: WasmDriverData,
159109
}
160110

161111
impl WasmDriver {
@@ -166,7 +116,6 @@ impl WasmDriver {
166116
}
167117
}
168118

169-
#[async_trait]
170119
impl GenericDriver for WasmDriver {
171120
fn name(&self) -> &String {
172121
&self.name
@@ -210,7 +159,12 @@ impl GenericDriver for WasmDriver {
210159
}
211160

212161
impl WasmDriver {
213-
fn new(cloud_identifier: &str, name: &str, source: &Source) -> Result<Arc<Self>> {
162+
fn new(
163+
config: &WasmConfig,
164+
cloud_identifier: &str,
165+
name: &str,
166+
source: &Source,
167+
) -> Result<Arc<Self>> {
214168
let config_directory = Storage::get_config_folder_for_driver(name);
215169
let data_directory = Storage::get_data_folder_for_driver(name);
216170
if !config_directory.exists() {
@@ -230,26 +184,46 @@ impl WasmDriver {
230184
});
231185
}
232186

233-
let mut config = Config::new();
234-
config.wasm_component_model(true);
187+
let mut engine_config = Config::new();
188+
engine_config.wasm_component_model(true);
235189
if let Err(error) =
236-
config.cache_config_load(Storage::get_configs_folder().join(CACHE_CONFIG_FILE))
190+
engine_config.cache_config_load(Storage::get_configs_folder().join(CONFIG_FILE))
237191
{
238192
warn!(
239193
"<red>Failed</> to enable caching for wasmtime engine: <red>{}</>",
240194
&error
241195
);
242196
}
243197

244-
let engine = Engine::new(&config)?;
198+
let engine = Engine::new(&engine_config)?;
245199
let component = Component::from_binary(&engine, &source.code)?;
246200

247201
let mut linker = Linker::new(&engine);
248202
wasmtime_wasi::add_to_linker_sync(&mut linker)?;
249203
Driver::add_to_linker(&mut linker, |state: &mut WasmDriverState| state)?;
250204

251-
let wasi = WasiCtxBuilder::new()
252-
.inherit_stdio()
205+
let mut wasi = WasiCtxBuilder::new();
206+
if let Some(config) = config.get_config(name) {
207+
if config.inherit_stdio {
208+
wasi.inherit_stdio();
209+
}
210+
if config.inherit_args {
211+
wasi.inherit_args();
212+
}
213+
if config.inherit_env {
214+
wasi.inherit_env();
215+
}
216+
if config.inherit_network {
217+
wasi.inherit_network();
218+
}
219+
if config.allow_ip_name_lookup {
220+
wasi.allow_ip_name_lookup(true);
221+
}
222+
for mount in &config.mounts {
223+
wasi.preopened_dir(&mount.host, &mount.guest, DirPerms::all(), FilePerms::all())?;
224+
}
225+
}
226+
let wasi = wasi
253227
.preopened_dir(
254228
&config_directory,
255229
"/configs/",
@@ -258,6 +232,7 @@ impl WasmDriver {
258232
)?
259233
.preopened_dir(&data_directory, "/data/", DirPerms::all(), FilePerms::all())?
260234
.build();
235+
261236
let table = ResourceTable::new();
262237

263238
let mut store = Store::new(
@@ -276,6 +251,9 @@ impl WasmDriver {
276251
name: name.to_string(),
277252
bindings,
278253
handle: Mutex::new(None),
254+
data: WasmDriverData {
255+
child_processes: RwLock::new(HashMap::new()),
256+
},
279257
}
280258
});
281259
let driver_resource = driver
@@ -291,17 +269,27 @@ impl WasmDriver {
291269
Ok(driver)
292270
}
293271

294-
pub fn load_all(cloud_identifier: &str, drivers: &mut Vec<Arc<dyn GenericDriver>>) {
272+
pub fn load_all(
273+
cloud_identifier: &str,
274+
drivers: &mut Vec<Arc<dyn GenericDriver>>,
275+
) -> WasmConfig {
295276
// Check if cache configuration exists
296-
let cache_config = Storage::get_configs_folder().join(CACHE_CONFIG_FILE);
297-
if !cache_config.exists() {
298-
fs::write(&cache_config, DEFAULT_CACHE_CONFIG).unwrap_or_else(|error| {
277+
let config_file = Storage::get_configs_folder().join(CONFIG_FILE);
278+
if !config_file.exists() {
279+
fs::write(&config_file, DEFAULT_CONFIG).unwrap_or_else(|error| {
299280
warn!(
300-
"<red>Failed</> to create default cache configuration file: <red>{}</>",
281+
"<red>Failed</> to create default wasm configuration file: <red>{}</>",
301282
&error
302283
)
303284
});
304285
}
286+
let config = WasmConfig::load_from_file(&config_file).unwrap_or_else(|error| {
287+
warn!(
288+
"<red>Failed</> to load wasm configuration file: <red>{}</>",
289+
&error
290+
);
291+
WasmConfig::default()
292+
});
305293

306294
let old_loaded = drivers.len();
307295

@@ -322,7 +310,7 @@ impl WasmDriver {
322310
"<red>Failed</> to read driver directory: <red>{}</>",
323311
&error
324312
);
325-
return;
313+
return config;
326314
}
327315
};
328316

@@ -365,7 +353,7 @@ impl WasmDriver {
365353
};
366354

367355
info!("Compiling driver <blue>{}</>...", &name);
368-
let driver = WasmDriver::new(cloud_identifier, &name, &source);
356+
let driver = WasmDriver::new(&config, cloud_identifier, &name, &source);
369357
match driver {
370358
Ok(driver) => match driver.init() {
371359
Ok(info) => {
@@ -399,6 +387,7 @@ impl WasmDriver {
399387
"The Wasm driver feature is <yellow>enabled</>, but no Wasm drivers were loaded."
400388
);
401389
}
390+
config
402391
}
403392
}
404393

0 commit comments

Comments
 (0)