mod context;
#[cfg(feature = "docker-backend")]
mod docker;
#[cfg(feature = "firecracker")]
mod firecracker;
mod local;
mod ssh;
mod r#trait;
pub use context::{Capability, ExecutionContext, ExecutionResult, SnapshotId, ValidationResult};
#[cfg(feature = "docker-backend")]
pub use docker::DockerExecutor;
#[cfg(feature = "firecracker")]
pub use firecracker::FirecrackerExecutor;
pub use local::LocalExecutor;
pub use ssh::SshExecutor;
pub use r#trait::ExecutionEnvironment;
use crate::config::{BackendType, RlmConfig};
use crate::error::RlmError;
use crate::validator::{KnowledgeGraphValidator, ValidatorConfig};
use std::sync::Arc;
fn build_validator_for_executor(config: &RlmConfig) -> Option<Arc<KnowledgeGraphValidator>> {
if config.thesaurus.is_none() && config.kg_strictness == crate::config::KgStrictness::Permissive
{
return None;
}
let vcfg = match config.kg_strictness {
crate::config::KgStrictness::Permissive => ValidatorConfig::permissive(),
crate::config::KgStrictness::Normal => ValidatorConfig::default(),
crate::config::KgStrictness::Strict => ValidatorConfig::strict(),
};
let mut validator = KnowledgeGraphValidator::new(vcfg);
if let Some(ref thesaurus) = config.thesaurus {
validator = validator.with_thesaurus(thesaurus.clone());
}
Some(Arc::new(validator))
}
pub fn is_kvm_available() -> bool {
std::path::Path::new("/dev/kvm").exists()
}
pub fn is_docker_available() -> bool {
std::process::Command::new("docker")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn is_gvisor_available() -> bool {
std::process::Command::new("runsc")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub async fn select_executor(
config: &RlmConfig,
) -> Result<Box<dyn ExecutionEnvironment<Error = RlmError> + Send + Sync>, RlmError> {
let backends = if config.backend_preference.is_empty() {
vec![
BackendType::Firecracker,
BackendType::E2b,
BackendType::Docker,
BackendType::Local,
]
} else {
config.backend_preference.clone()
};
let validator = build_validator_for_executor(config);
#[cfg(feature = "docker-backend")]
let docker_available = is_docker_available();
let mut tried = Vec::new();
for backend in backends {
match backend {
#[cfg(feature = "firecracker")]
BackendType::Firecracker if is_kvm_available() => {
log::info!("Selected Firecracker backend (KVM available)");
let executor = FirecrackerExecutor::new(config.clone(), validator.clone())?;
if let Err(e) = executor.initialize().await {
log::warn!(
"Failed to initialize FirecrackerExecutor: {}. Trying next backend.",
e
);
tried.push(format!("firecracker (init failed: {})", e));
continue;
}
return Ok(Box::new(executor));
}
#[cfg(feature = "firecracker")]
BackendType::Firecracker => {
log::debug!("Firecracker unavailable: KVM not present");
tried.push("firecracker (no KVM)".to_string());
}
#[cfg(not(feature = "firecracker"))]
BackendType::Firecracker => {
log::debug!("Firecracker backend disabled at compile time");
tried.push("firecracker (compile-time disabled)".to_string());
}
BackendType::E2b if config.e2b_api_key.is_some() => {
log::debug!("E2B backend not yet implemented; trying next backend");
tried.push("e2b (not implemented)".to_string());
}
BackendType::E2b => {
log::debug!("E2B unavailable: no API key configured");
tried.push("e2b (no API key)".to_string());
}
#[cfg(feature = "docker-backend")]
BackendType::Docker if docker_available => {
match DockerExecutor::new(config.clone(), validator.clone()) {
Ok(executor) => {
log::info!("Selected Docker backend (container isolation)");
return Ok(Box::new(executor));
}
Err(e) => {
log::warn!("DockerExecutor init failed: {}. Trying next backend.", e);
tried.push(format!("docker (init failed: {})", e));
}
}
}
#[cfg(feature = "docker-backend")]
BackendType::Docker => {
log::debug!("Docker unavailable: CLI not present");
tried.push("docker (not available)".to_string());
}
#[cfg(not(feature = "docker-backend"))]
BackendType::Docker => {
log::debug!("Docker backend disabled at compile time");
tried.push("docker (compile-time disabled)".to_string());
}
BackendType::Local => {
log::warn!(
"Falling back to LocalExecutor (NO ISOLATION). Tried: {:?}",
tried
);
return Ok(Box::new(LocalExecutor::new().with_validator(validator)));
}
}
}
Err(RlmError::NoBackendAvailable { tried })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kvm_check() {
let _ = is_kvm_available();
}
#[test]
fn test_docker_check() {
let _ = is_docker_available();
}
#[test]
fn test_gvisor_check() {
let _ = is_gvisor_available();
}
#[tokio::test]
async fn test_select_executor_local_preference_returns_local() {
let config = RlmConfig {
backend_preference: vec![BackendType::Local],
..Default::default()
};
let executor = select_executor(&config).await.expect("should select Local");
assert_eq!(executor.backend_type(), BackendType::Local);
}
#[tokio::test]
async fn test_select_executor_e2b_unimplemented_falls_through_to_local() {
let config = RlmConfig {
backend_preference: vec![BackendType::E2b, BackendType::Local],
e2b_api_key: Some("dummy".to_string()),
..Default::default()
};
let executor = select_executor(&config)
.await
.expect("should fall through to Local");
assert_eq!(executor.backend_type(), BackendType::Local);
}
}