use crate::cloudhv::config::{CloudHypervisorConfig, VMConfig, VMSpec, ConsoleMode, SerialMode};
use crate::cloudhv::errors::{CloudHypervisorError, Result};
use crate::cloudhv::runtime::{CloudHypervisorRuntime, VMHandle};
use rhai::{Dynamic, Engine, AST, EvalAltResult};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use once_cell::sync::Lazy;
static GLOBAL_RUNTIME: Lazy<Arc<Mutex<Option<Arc<CloudHypervisorRuntime>>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
pub fn register_cloudhv_module(engine: &mut Engine) -> std::result::Result<(), Box<EvalAltResult>> {
engine
.register_type::<RuntimeBuilder>()
.register_fn("RuntimeBuilder", RuntimeBuilder::new)
.register_fn("ch_path", RuntimeBuilder::ch_path)
.register_fn("api_socket", RuntimeBuilder::api_socket)
.register_fn("log_file", RuntimeBuilder::log_file)
.register_fn("seccomp", RuntimeBuilder::seccomp)
.register_fn("landlock", RuntimeBuilder::landlock)
.register_fn("start", RuntimeBuilder::start);
engine
.register_type::<VMBuilder>()
.register_fn("VMBuilder", VMBuilder::new)
.register_fn("kernel", VMBuilder::kernel)
.register_fn("initramfs", VMBuilder::initramfs)
.register_fn("cmdline", VMBuilder::cmdline)
.register_fn("cmdline", VMBuilder::cmdline_string)
.register_fn("memory", VMBuilder::memory)
.register_fn("memory_shared", VMBuilder::memory_shared)
.register_fn("cpus", VMBuilder::cpus)
.register_fn("virtio_blk", VMBuilder::virtio_blk)
.register_fn("virtio_net", VMBuilder::virtio_net)
.register_fn("virtio_net_with_mac", VMBuilder::virtio_net_with_mac)
.register_fn("virtio_rng", VMBuilder::virtio_rng)
.register_fn("virtio_fs", VMBuilder::virtio_fs)
.register_fn("vhost_user_net", VMBuilder::vhost_user_net)
.register_fn("vhost_user_blk", VMBuilder::vhost_user_blk)
.register_fn("vfio", VMBuilder::vfio)
.register_fn("console", VMBuilder::console)
.register_fn("serial", VMBuilder::serial)
.register_fn("build", VMBuilder::build);
engine
.register_type::<DeviceBuilder>()
.register_fn("DeviceBuilder", DeviceBuilder::new)
.register_fn("disk", DeviceBuilder::disk)
.register_fn("net", DeviceBuilder::net)
.register_fn("net_with_mac", DeviceBuilder::net_with_mac)
.register_fn("fs", DeviceBuilder::fs)
.register_fn("vhost_user_net", DeviceBuilder::vhost_user_net)
.register_fn("vhost_user_blk", DeviceBuilder::vhost_user_blk);
engine
.register_type::<VMSpec>()
.register_get("id", |spec: &mut VMSpec| spec.id.clone());
engine
.register_type::<VMHandle>()
.register_get("id", |handle: &mut VMHandle| handle.id.clone());
engine.register_fn("sleep", |ms: i64| {
std::thread::sleep(std::time::Duration::from_millis(ms as u64));
});
engine.register_fn("init_runtime", |builder: RuntimeBuilder| -> bool {
let rt = builder.build_and_start();
match rt {
Ok(r) => {
*GLOBAL_RUNTIME.lock().unwrap() = Some(Arc::new(r));
true
}
Err(e) => {
eprintln!("\n❌ ERROR: Failed to initialize runtime");
eprintln!(" {}", e);
eprintln!("\n Script execution will continue but all VM operations will fail.");
eprintln!(" Please fix the error and try again.\n");
false
}
}
});
engine.register_fn("create_vm", |spec: VMSpec| -> VMHandle {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let handle = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.create_vm(spec).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.create_vm(spec).await })
};
match handle {
Ok(h) => h,
Err(e) => {
panic!("Failed to create VM: {}", e);
}
}
});
engine.register_fn("start_vm", |handle: VMHandle| {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.start_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.start_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to start VM: {}", e),
}
});
engine.register_fn("stop_vm", |handle: VMHandle| {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.stop_vm(&handle, false).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.stop_vm(&handle, false).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to stop VM: {}", e),
}
});
engine.register_fn("pause_vm", |handle: VMHandle| {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.pause_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.pause_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to pause VM: {}", e),
}
});
engine.register_fn("resume_vm", |handle: VMHandle| {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.resume_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.resume_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to resume VM: {}", e),
}
});
engine.register_fn("reset_vm", |handle: VMHandle| {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.reset_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.reset_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to reset VM: {}", e),
}
});
engine.register_fn("get_vm_status", |handle: VMHandle| -> String {
let runtime = match GLOBAL_RUNTIME.lock().unwrap().clone() {
Some(rt) => rt,
None => {
eprintln!("Runtime not initialized. Call init_runtime() first.");
return "NotInitialized".to_string();
}
};
let status = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.get_vm_status(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.get_vm_status(&handle).await })
};
match status {
Ok(s) => format!("{:?}", s),
Err(_) => "Unknown".to_string(),
}
});
Ok(())
}
pub struct ScriptEngine {
engine: Engine,
runtime: Arc<Mutex<Option<Arc<CloudHypervisorRuntime>>>>,
}
impl ScriptEngine {
pub fn new() -> Self {
let mut engine = Engine::new();
let runtime = Arc::new(Mutex::new(None));
register_cloudhv_module(&mut engine).expect("Failed to register cloudhv module");
let runtime_clone = Arc::clone(&runtime);
engine.register_fn("init_runtime", move |builder: RuntimeBuilder| -> bool {
let rt = builder.build_and_start();
match rt {
Ok(r) => {
*runtime_clone.lock().unwrap() = Some(Arc::new(r));
true
}
Err(e) => {
eprintln!("\n❌ ERROR: Failed to initialize runtime");
eprintln!(" {}", e);
eprintln!("\n Script execution will continue but all VM operations will fail.");
eprintln!(" Please fix the error and try again.\n");
false
}
}
});
Self { engine, runtime }
}
pub fn new_with_runtime(runtime: Arc<CloudHypervisorRuntime>) -> Self {
let mut engine = Engine::new();
let runtime_wrapper = Arc::new(Mutex::new(Some(runtime)));
engine
.register_type::<RuntimeBuilder>()
.register_fn("RuntimeBuilder", RuntimeBuilder::new)
.register_fn("ch_path", RuntimeBuilder::ch_path)
.register_fn("api_socket", RuntimeBuilder::api_socket)
.register_fn("log_file", RuntimeBuilder::log_file)
.register_fn("seccomp", RuntimeBuilder::seccomp)
.register_fn("landlock", RuntimeBuilder::landlock)
.register_fn("start", RuntimeBuilder::start);
let runtime_clone = Arc::clone(&runtime_wrapper);
engine.register_fn("init_runtime", move |builder: RuntimeBuilder| -> bool {
let rt = builder.build_and_start();
match rt {
Ok(r) => {
*runtime_clone.lock().unwrap() = Some(Arc::new(r));
true
}
Err(e) => {
eprintln!("\n❌ ERROR: Failed to initialize runtime");
eprintln!(" {}", e);
eprintln!("\n Script execution will continue but all VM operations will fail.");
eprintln!(" Please fix the error and try again.\n");
false
}
}
});
Self { engine, runtime: runtime_wrapper }
}
pub fn eval_script(&mut self, script: &str) -> Result<Dynamic> {
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("create_vm", move |spec: VMSpec| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let handle = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.create_vm(spec).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.create_vm(spec).await })
};
match handle {
Ok(h) => h,
Err(e) => {
panic!("Failed to create VM: {}", e);
}
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("start_vm", move |handle: VMHandle| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.start_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.start_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to start VM: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("stop_vm", move |handle: VMHandle| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.stop_vm(&handle, false).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.stop_vm(&handle, false).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to stop VM: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("pause_vm", move |handle: VMHandle| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.pause_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.pause_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to pause VM: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("resume_vm", move |handle: VMHandle| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.resume_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.resume_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to resume VM: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("get_vm_status", move || -> String {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
eprintln!("Runtime not initialized. Call init_runtime() first.");
return "NotInitialized".to_string();
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
match runtime.get_api_client() {
Ok(client) => {
match client.vm_info().await {
Ok(info) => info.state,
Err(e) => {
eprintln!("VM info error: {}", e);
"Unknown".to_string()
}
}
}
Err(e) => {
eprintln!("API client error: {}", e);
"NoAPI".to_string()
}
}
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async {
match runtime.get_api_client() {
Ok(client) => {
match client.vm_info().await {
Ok(info) => info.state,
Err(e) => {
eprintln!("VM info error: {}", e);
"Unknown".to_string()
}
}
}
Err(e) => {
eprintln!("API client error: {}", e);
"NoAPI".to_string()
}
}
})
};
result
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine
.register_fn("snapshot", move |handle: VMHandle, path: String| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let snapshot_path = PathBuf::from(path);
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.create_snapshot(&handle, &snapshot_path).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.create_snapshot(&handle, &snapshot_path).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to create snapshot: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("restore", move |path: String| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let snapshot_path = PathBuf::from(path);
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.restore_snapshot(&snapshot_path).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.restore_snapshot(&snapshot_path).await })
};
match result {
Ok(handle) => handle,
Err(e) => {
panic!("Failed to restore snapshot: {}", e);
}
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("reset_vm", move |handle: VMHandle| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.reset_vm(&handle).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.reset_vm(&handle).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to reset VM: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine
.register_fn("resize_memory", move |handle: VMHandle, size_mb: i64| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.resize_memory(&handle, size_mb as u64).await
})
})
} else {
tokio::runtime::Runtime::new().unwrap().block_on(async {
runtime.resize_memory(&handle, size_mb as u64).await
})
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to resize memory: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine
.register_fn("add_vcpu", move |handle: VMHandle, count: i64| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.add_vcpu(&handle, count as u32).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.add_vcpu(&handle, count as u32).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to add vCPU: {}", e),
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("list_snapshots", move |vm_id: String| -> Vec<Dynamic> {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
eprintln!("Runtime not initialized. Call init_runtime() first.");
return vec![];
}
};
match runtime.list_snapshots(&vm_id) {
Ok(snapshots) => {
snapshots.into_iter().map(|s| {
let mut map = rhai::Map::new();
map.insert("vm_id".into(), Dynamic::from(s.vm_id));
map.insert("name".into(), Dynamic::from(s.name));
map.insert("path".into(), Dynamic::from(s.path.to_string_lossy().to_string()));
map.insert("timestamp".into(), Dynamic::from(s.timestamp));
map.insert("size".into(), Dynamic::from(s.size as i64));
Dynamic::from(map)
}).collect()
}
Err(e) => {
eprintln!("Failed to list snapshots: {}", e);
vec![]
}
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("delete_snapshot", move |vm_id: String, name: String| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
eprintln!("Runtime not initialized. Call init_runtime() first.");
return false;
}
};
runtime.delete_snapshot(&vm_id, &name).is_ok()
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine.register_fn("add_device", move |handle: VMHandle, builder: DeviceBuilder| -> String {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
use crate::cloudhv::config::{DeviceConfig, DeviceType, DiskConfig, NetConfig, FsConfig, VhostUserNetConfig, VhostUserBlkConfig};
use std::path::PathBuf;
let device_type = match builder.get_type().as_str() {
"disk" => {
let config = builder.get_config();
let path = config.get("path").and_then(|v| v.clone().into_string().ok()).unwrap_or_default();
let readonly = config.get("readonly").and_then(|v| v.as_bool().ok()).unwrap_or(false);
DeviceType::Disk(DiskConfig {
path: PathBuf::from(path),
readonly,
direct: false,
vhost_user: false,
socket: None,
})
}
"net" => {
let config = builder.get_config();
let tap = config.get("tap").and_then(|v| v.clone().into_string().ok());
let mac = config.get("mac").and_then(|v| v.clone().into_string().ok());
DeviceType::Net(NetConfig {
tap,
mac,
vhost_user: false,
socket: None,
})
}
"fs" => {
let config = builder.get_config();
let tag = config.get("tag").and_then(|v| v.clone().into_string().ok()).unwrap_or_default();
let socket = config.get("socket").and_then(|v| v.clone().into_string().ok()).unwrap_or_default();
DeviceType::Fs(FsConfig {
tag,
socket: PathBuf::from(socket),
num_queues: 1,
cache_size: 8589934592, queue_size: 1024,
})
}
"vhost_user_net" => {
let config = builder.get_config();
let socket = config.get("socket").and_then(|v| v.clone().into_string().ok()).unwrap_or_default();
DeviceType::VhostUserNet(VhostUserNetConfig {
socket: PathBuf::from(socket),
mac: None,
num_queues: 2,
queue_size: 256,
})
}
"vhost_user_blk" => {
let config = builder.get_config();
let socket = config.get("socket").and_then(|v| v.clone().into_string().ok()).unwrap_or_default();
let readonly = config.get("readonly").and_then(|v| v.as_bool().ok()).unwrap_or(false);
DeviceType::VhostUserBlk(VhostUserBlkConfig {
socket: PathBuf::from(socket),
num_queues: 1,
queue_size: 128,
readonly,
})
}
_ => {
eprintln!("Unknown device type: {}", builder.get_type());
return "error".to_string();
}
};
let device = DeviceConfig {
id: builder.get_id(),
device_type,
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.add_device(&handle, device).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.add_device(&handle, device).await })
};
match result {
Ok(id) => id,
Err(e) => {
panic!("Failed to add device: {}", e);
}
}
});
let runtime_ref = Arc::clone(&self.runtime);
self.engine
.register_fn("remove_device", move |handle: VMHandle, device_id: String| {
let runtime = match runtime_ref.lock().unwrap().clone() {
Some(rt) => rt,
None => {
panic!("Runtime not initialized. Call init_runtime() first.");
}
};
let result = if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
runtime.remove_device(&handle, &device_id).await
})
})
} else {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async { runtime.remove_device(&handle, &device_id).await })
};
match result {
Ok(_) => {},
Err(e) => panic!("Failed to remove device: {}", e),
}
});
self.engine
.eval::<Dynamic>(script)
.map_err(|e| CloudHypervisorError::Script(format!("Script error: {}", e)))
}
pub fn compile_script(&self, script: &str) -> Result<AST> {
self.engine
.compile(script)
.map_err(|e| CloudHypervisorError::Script(format!("Compilation error: {}", e)))
}
pub fn eval_ast(&mut self, ast: &AST) -> Result<Dynamic> {
self.engine
.eval_ast::<Dynamic>(ast)
.map_err(|e| CloudHypervisorError::Script(format!("Execution error: {}", e)))
}
}
#[derive(Clone)]
pub struct VMBuilder {
config: VMConfig,
}
impl VMBuilder {
pub fn new() -> Self {
Self {
config: VMConfig::new(),
}
}
pub fn kernel(mut self, path: String) -> Self {
self.config = self.config.with_kernel(path);
self
}
pub fn initramfs(mut self, path: String) -> Self {
self.config = self.config.with_initramfs(path);
self
}
pub fn cmdline(mut self, args: Vec<Dynamic>) -> Self {
let string_args: Vec<String> = args
.into_iter()
.filter_map(|d| d.into_string().ok())
.collect();
self.config = self.config.with_cmdline(string_args);
self
}
pub fn cmdline_string(mut self, cmdline: String) -> Self {
let args: Vec<String> = cmdline.split_whitespace().map(|s| s.to_string()).collect();
self.config = self.config.with_cmdline(args);
self
}
pub fn memory(mut self, size_mb: i64) -> Self {
self.config = self.config.with_memory(size_mb as u64);
self
}
pub fn memory_shared(mut self, shared: bool) -> Self {
self.config = self.config.with_memory_shared(shared);
self
}
pub fn cpus(mut self, count: i64) -> Self {
self.config = self.config.with_cpus(count as u32);
self
}
pub fn virtio_blk(mut self, path: String, readonly: bool) -> Self {
self.config.add_virtio_blk(path, readonly);
self
}
pub fn virtio_net(mut self, tap: String) -> Self {
self.config.add_virtio_net(tap);
self
}
pub fn virtio_net_with_mac(mut self, tap: String, mac: String) -> Self {
self.config.add_virtio_net_with_mac(tap, mac);
self
}
pub fn virtio_rng(mut self) -> Self {
self.config.add_virtio_rng();
self
}
pub fn virtio_fs(mut self, tag: String, path: String) -> Self {
self.config.add_virtio_fs(tag, path);
self
}
pub fn vhost_user_net(mut self, socket: String) -> Self {
self.config.add_vhost_user_net(socket);
self
}
pub fn vhost_user_blk(mut self, socket: String) -> Self {
self.config.add_vhost_user_blk(socket);
self
}
pub fn vfio(mut self, device: String) -> Self {
self.config.add_vfio(device);
self
}
pub fn console(mut self, mode: String) -> Self {
self.config.console.mode = match mode.as_str() {
"tty" => ConsoleMode::Tty,
"off" => ConsoleMode::Off,
"file" => ConsoleMode::File,
"null" => ConsoleMode::Null,
_ => ConsoleMode::Off,
};
self
}
pub fn serial(mut self, mode: String) -> Self {
self.config.serial.mode = match mode.as_str() {
"tty" => SerialMode::Tty,
"off" => SerialMode::Off,
"file" => SerialMode::File,
"null" => SerialMode::Null,
_ => SerialMode::Null,
};
self
}
pub fn build(self) -> VMSpec {
self.config.build().unwrap_or_else(|e| {
eprintln!("Failed to build VM config: {}", e);
VMSpec {
config: VMConfig::default(),
id: "error".to_string(),
}
})
}
}
impl Default for VMBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct RuntimeBuilder {
ch_path: Option<String>,
api_socket: String,
log_file: Option<String>,
seccomp: bool,
landlock: bool,
}
impl RuntimeBuilder {
pub fn new() -> Self {
Self {
ch_path: None, api_socket: "/tmp/ch-vm.sock".to_string(),
log_file: None,
seccomp: true,
landlock: true,
}
}
pub fn ch_path(mut self, path: String) -> Self {
self.ch_path = Some(path);
self
}
pub fn api_socket(mut self, path: String) -> Self {
self.api_socket = path;
self
}
pub fn log_file(mut self, path: String) -> Self {
self.log_file = Some(path);
self
}
pub fn seccomp(mut self, enabled: bool) -> Self {
self.seccomp = enabled;
self
}
pub fn landlock(mut self, enabled: bool) -> Self {
self.landlock = enabled;
self
}
pub fn start(self) -> Self {
self
}
pub fn build_and_start(self) -> Result<CloudHypervisorRuntime> {
let mut config = CloudHypervisorConfig::new_smart(self.ch_path)?
.with_api_socket(&self.api_socket)
.with_seccomp(self.seccomp)
.with_landlock(self.landlock);
if let Some(log) = self.log_file {
config = config.with_log_file(log);
}
let runtime = CloudHypervisorRuntime::new(config)?;
runtime.start()?;
Ok(runtime)
}
}
impl Default for RuntimeBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct DeviceBuilder {
device_id: String,
device_type: Option<String>,
config: rhai::Map,
}
impl DeviceBuilder {
pub fn new(id: String) -> Self {
Self {
device_id: id,
device_type: None,
config: rhai::Map::new(),
}
}
pub fn disk(mut self, path: String, readonly: bool) -> Self {
self.device_type = Some("disk".to_string());
self.config.insert("path".into(), Dynamic::from(path));
self.config.insert("readonly".into(), Dynamic::from(readonly));
self
}
pub fn net(mut self, tap: String) -> Self {
self.device_type = Some("net".to_string());
self.config.insert("tap".into(), Dynamic::from(tap));
self
}
pub fn net_with_mac(mut self, tap: String, mac: String) -> Self {
self.device_type = Some("net".to_string());
self.config.insert("tap".into(), Dynamic::from(tap));
self.config.insert("mac".into(), Dynamic::from(mac));
self
}
pub fn fs(mut self, tag: String, socket: String) -> Self {
self.device_type = Some("fs".to_string());
self.config.insert("tag".into(), Dynamic::from(tag));
self.config.insert("socket".into(), Dynamic::from(socket));
self
}
pub fn vhost_user_net(mut self, socket: String) -> Self {
self.device_type = Some("vhost_user_net".to_string());
self.config.insert("socket".into(), Dynamic::from(socket));
self
}
pub fn vhost_user_blk(mut self, socket: String, readonly: bool) -> Self {
self.device_type = Some("vhost_user_blk".to_string());
self.config.insert("socket".into(), Dynamic::from(socket));
self.config.insert("readonly".into(), Dynamic::from(readonly));
self
}
pub fn get_id(&self) -> String {
self.device_id.clone()
}
pub fn get_type(&self) -> String {
self.device_type.clone().unwrap_or_default()
}
pub fn get_config(&self) -> rhai::Map {
self.config.clone()
}
}
impl Default for DeviceBuilder {
fn default() -> Self {
Self::new("device0".to_string())
}
}