use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use crate::error::{Result, RightsizeError};
use crate::model::{ContainerSpec, ExecResult};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Capabilities {
pub hardware_isolated: bool,
pub checkpoint: bool,
pub checkpoint_restarts_workload: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NetworkLink {
pub alias: String,
pub guest_port: u16,
pub target_host_port: u16,
}
pub trait SandboxHandle: Send + Sync {
fn id(&self) -> &str;
fn spec(&self) -> &ContainerSpec;
}
#[async_trait::async_trait]
pub trait SandboxBackend: Send + Sync {
fn name(&self) -> &str;
fn supports_native_networks(&self) -> bool;
fn capabilities(&self) -> Capabilities {
Capabilities::default()
}
async fn create(&self, spec: ContainerSpec) -> Result<Box<dyn SandboxHandle>>;
async fn start(&self, handle: &dyn SandboxHandle) -> Result<()>;
async fn stop(&self, handle: &dyn SandboxHandle) -> Result<()>;
async fn remove(&self, handle: &dyn SandboxHandle) -> Result<()>;
async fn exec(&self, handle: &dyn SandboxHandle, cmd: &[String]) -> Result<ExecResult>;
async fn logs(&self, handle: &dyn SandboxHandle) -> Result<String>;
async fn follow_logs(
&self,
handle: &dyn SandboxHandle,
consumer: Box<dyn Fn(String) + Send + Sync>,
) -> Result<FollowHandle>;
async fn ensure_network(&self, network_id: &str) -> Result<()>;
async fn remove_network(&self, network_id: &str) -> Result<()>;
async fn install_network_links(
&self,
_handle: &dyn SandboxHandle,
_links: &[NetworkLink],
) -> Result<()> {
Ok(())
}
async fn close(&self) -> Result<()> {
Ok(())
}
fn cleanup_sync(&self, container_id: &str);
fn remove_by_name(&self, name: &str);
fn watchdog_kill_command(&self) -> Vec<String>;
fn watchdog_network_kill_command(&self) -> Vec<String> {
Vec::new()
}
fn backend_binary_path(&self) -> Option<std::path::PathBuf> {
None
}
async fn find_running(&self, _spec: &ContainerSpec) -> Result<Option<Box<dyn SandboxHandle>>> {
Ok(None)
}
async fn create_checkpoint(&self, _handle: &dyn SandboxHandle, _nonce: &str) -> Result<String> {
Err(crate::error::RightsizeError::CheckpointUnsupported {
backend: self.name().to_string(),
})
}
async fn remove_checkpoint(&self, _checkpoint_ref: &str) -> Result<()> {
Ok(())
}
async fn has_checkpoint(&self, _checkpoint_ref: &str) -> Result<bool> {
Err(RightsizeError::unsupported(
"checkpoint existence probe",
self.name(),
))
}
async fn export_checkpoint(&self, _checkpoint_ref: &str, _dest_file: &Path) -> Result<()> {
Err(RightsizeError::unsupported(
"checkpoint export",
self.name(),
))
}
async fn import_checkpoint(&self, _src_file: &Path, _ref_hint: &str) -> Result<String> {
Err(RightsizeError::unsupported(
"checkpoint import",
self.name(),
))
}
async fn copy_to_container(
&self,
_handle: &dyn SandboxHandle,
_host_path: &Path,
_container_path: &str,
) -> Result<()> {
Err(RightsizeError::unsupported(
"runtime file copy",
self.name(),
))
}
async fn copy_from_container(
&self,
_handle: &dyn SandboxHandle,
_container_path: &str,
_host_path: &Path,
) -> Result<()> {
Err(RightsizeError::unsupported(
"runtime file copy",
self.name(),
))
}
}
pub trait BackendProvider: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> u32;
fn is_supported(&self) -> bool;
fn unsupported_reason(&self) -> String;
fn create(&self) -> Result<Box<dyn SandboxBackend>>;
}
enum JoinTarget {
Thread(Option<std::thread::JoinHandle<()>>),
Task(Option<tokio::task::JoinHandle<()>>),
}
impl JoinTarget {
fn join_best_effort(&mut self) {
match self {
JoinTarget::Thread(h) => {
if let Some(h) = h.take() {
let _ = h.join();
}
}
JoinTarget::Task(h) => {
if let Some(h) = h.take() {
h.abort();
}
}
}
}
}
pub struct FollowHandle {
close_requested: Arc<AtomicBool>,
joiners: Vec<JoinTarget>,
}
impl FollowHandle {
fn new(close_requested: Arc<AtomicBool>, joiners: Vec<JoinTarget>) -> Self {
Self {
close_requested,
joiners,
}
}
pub fn from_threads(
close_requested: Arc<AtomicBool>,
threads: Vec<std::thread::JoinHandle<()>>,
) -> Self {
Self::new(
close_requested,
threads
.into_iter()
.map(|h| JoinTarget::Thread(Some(h)))
.collect(),
)
}
pub fn from_task(close_requested: Arc<AtomicBool>, task: tokio::task::JoinHandle<()>) -> Self {
Self::new(close_requested, vec![JoinTarget::Task(Some(task))])
}
pub fn close(mut self) {
self.close_requested
.store(true, std::sync::atomic::Ordering::SeqCst);
for j in &mut self.joiners {
j.join_best_effort();
}
}
}
impl Drop for FollowHandle {
fn drop(&mut self) {
self.close_requested
.store(true, std::sync::atomic::Ordering::SeqCst);
for j in &mut self.joiners {
j.join_best_effort();
}
}
}
pub use crate::futures::BoxFuture;
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
#[test]
fn follow_handle_close_sets_close_requested_and_joins_threads() {
let flag = Arc::new(AtomicBool::new(false));
let joined = Arc::new(AtomicBool::new(false));
let joined_clone = joined.clone();
let handle = std::thread::spawn(move || {
while !joined_clone.load(Ordering::SeqCst) {
std::thread::sleep(std::time::Duration::from_millis(1));
}
});
let fh = FollowHandle::from_threads(flag.clone(), vec![handle]);
joined.store(true, Ordering::SeqCst);
fh.close();
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn follow_handle_drop_also_sets_close_requested() {
let flag = Arc::new(AtomicBool::new(false));
{
let handle = std::thread::spawn(|| {});
let _fh = FollowHandle::from_threads(flag.clone(), vec![handle]);
}
assert!(flag.load(Ordering::SeqCst));
}
}