pub mod inlined;
pub mod probe;
#[cfg(feature = "sibling")]
pub mod sibling;
pub use inlined::Inlined;
pub use probe::{BackendAvailability, BackendProbe};
#[cfg(feature = "containerd-integration")]
pub mod containerd;
#[cfg(feature = "docker-integration")]
pub mod docker;
#[cfg(feature = "native-integration")]
pub mod native;
#[cfg(feature = "testing")]
pub mod fake;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::pin::Pin;
use std::time::{SystemTime, UNIX_EPOCH};
use async_trait::async_trait;
use futures_core::Stream;
use serde::{Deserialize, Serialize};
pub use workload_spec::{MeshIdent, WorkloadSpec};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Backend {
Native,
Containerd,
Docker,
}
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[error("backend {backend:?} is not available on this host: {detail}")]
pub struct BackendUnavailable {
pub backend: Backend,
pub detail: String,
pub install_hint: Option<String>,
}
pub type LogStream = Pin<Box<dyn Stream<Item = LogEvent> + Send + 'static>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshAssignment {
pub mesh_ip: Ipv4Addr,
pub wg_private_key: String,
pub wg_listen_port: u16,
pub peers: Vec<WireguardPeer>,
pub netns_name: Option<String>,
}
impl MeshAssignment {
pub fn inlined(mesh_ip: Ipv4Addr) -> Self {
MeshAssignment {
mesh_ip,
wg_private_key: String::new(),
wg_listen_port: 0,
peers: Vec::new(),
netns_name: None,
}
}
pub fn stub(mesh_ip: Ipv4Addr) -> Self {
Self::inlined(mesh_ip)
}
pub fn has_wireguard(&self) -> bool {
!self.wg_private_key.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WireguardPeer {
pub public_key: String,
pub endpoint: Option<SocketAddr>,
pub allowed_ips: Vec<IpAddr>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployResult {
pub container_id: String,
pub mesh_ip: Ipv4Addr,
pub task_pid: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkloadState {
pub ident: MeshIdent,
pub container_id: String,
pub status: WorkloadStatus,
pub mesh_ip: Option<Ipv4Addr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WorkloadStatus {
Pending,
Running,
Stopping,
Stopped,
Restarting {
last_exit_code: i32,
restart_count: u32,
last_finished_at_unix_ms: u64,
},
Failed {
reason: String,
},
}
impl WorkloadStatus {
pub fn is_terminal(&self) -> bool {
matches!(self, WorkloadStatus::Stopped | WorkloadStatus::Failed { .. })
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LogOpts {
pub tail: Option<u64>,
pub follow: bool,
pub stream: Option<LogStreamKind>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LogStreamKind {
Stdout,
Stderr,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEvent {
pub timestamp_ms: u64,
pub ident: MeshIdent,
pub stream: LogStreamKind,
pub message: String,
pub correlation_id: Option<String>,
}
impl LogEvent {
pub fn plain(ident: MeshIdent, stream: LogStreamKind, message: impl Into<String>) -> Self {
let timestamp_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
LogEvent {
timestamp_ms,
ident,
stream,
message: message.into(),
correlation_id: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeHealth {
pub ok: bool,
pub version: Option<String>,
pub detail: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatefulServiceContract {
pub name: String,
pub files: Vec<String>,
pub vend_endpoint: Option<String>,
pub schema_version: String,
pub backup_target: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltinService {
pub name: &'static str,
pub files: &'static [&'static str],
pub vend_endpoint: Option<&'static str>,
pub schema_version: &'static str,
pub backup_target: Option<&'static str>,
}
impl BuiltinService {
pub const fn new(
name: &'static str,
files: &'static [&'static str],
schema_version: &'static str,
) -> Self {
Self {
name,
files,
vend_endpoint: None,
schema_version,
backup_target: None,
}
}
pub fn contract(&self) -> StatefulServiceContract {
StatefulServiceContract {
name: self.name.to_string(),
files: self.files.iter().map(|s| s.to_string()).collect(),
vend_endpoint: self.vend_endpoint.map(|s| s.to_string()),
schema_version: self.schema_version.to_string(),
backup_target: self.backup_target.map(|s| s.to_string()),
}
}
}
#[async_trait]
pub trait Kamaji: Send + Sync {
fn backend(&self) -> Backend;
async fn deploy_workload(
&self,
spec: &WorkloadSpec,
mesh: &MeshAssignment,
) -> anyhow::Result<DeployResult>;
async fn list_workloads(&self) -> anyhow::Result<Vec<WorkloadState>>;
async fn get_workload(
&self,
ident: &MeshIdent,
) -> anyhow::Result<Option<WorkloadState>>;
async fn stream_logs(
&self,
ident: &MeshIdent,
opts: LogOpts,
) -> anyhow::Result<LogStream>;
async fn restart_workload(&self, ident: &MeshIdent) -> anyhow::Result<()>;
async fn teardown_workload(&self, ident: &MeshIdent) -> anyhow::Result<()>;
async fn health(&self) -> anyhow::Result<RuntimeHealth>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workload_status_terminal_matrix() {
assert!(!WorkloadStatus::Pending.is_terminal());
assert!(!WorkloadStatus::Running.is_terminal());
assert!(!WorkloadStatus::Stopping.is_terminal());
assert!(WorkloadStatus::Stopped.is_terminal());
assert!(!WorkloadStatus::Restarting {
last_exit_code: 2,
restart_count: 3,
last_finished_at_unix_ms: 1,
}
.is_terminal());
assert!(WorkloadStatus::Failed {
reason: "boom".into()
}
.is_terminal());
}
#[test]
fn mesh_assignment_inlined_has_no_wireguard() {
let m = MeshAssignment::inlined(Ipv4Addr::new(127, 0, 0, 1));
assert!(!m.has_wireguard());
assert!(m.peers.is_empty());
assert_eq!(m.wg_listen_port, 0);
}
#[test]
fn backend_serde_round_trip() {
let json = serde_json::to_string(&Backend::Containerd).unwrap();
assert_eq!(json, "\"containerd\"");
let parsed: Backend = serde_json::from_str("\"docker\"").unwrap();
assert_eq!(parsed, Backend::Docker);
}
}