use crate::{
DependencyRequirement, RestartPolicy, ServiceActivationState, ServiceHealth,
ServiceRuntimeState, SupervisorResult,
};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManagedResource {
Runtime,
Security,
Scheduler,
PeerRpc,
ControlPlane,
Jobs,
Update,
AuthServer,
Metrics,
Observation,
Sync,
Http,
Gateway,
Worker,
Queue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceDependency {
service_id: String,
requirement: DependencyRequirement,
}
impl ServiceDependency {
pub fn new(
service_id: impl Into<String>,
requirement: DependencyRequirement,
) -> SupervisorResult<Self> {
let dependency = Self {
service_id: service_id.into(),
requirement,
};
validate_name(&dependency.service_id)?;
Ok(dependency)
}
pub fn service_id(&self) -> &str {
&self.service_id
}
pub fn requirement(&self) -> DependencyRequirement {
self.requirement
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceDescriptor {
name: String,
resource: ManagedResource,
dependencies: Vec<ServiceDependency>,
restart_policy: RestartPolicy,
activation: ServiceActivationState,
critical: bool,
}
impl ServiceDescriptor {
pub fn new(
name: impl Into<String>,
resource: ManagedResource,
restart_policy: RestartPolicy,
) -> SupervisorResult<Self> {
let descriptor = Self {
name: name.into(),
resource,
dependencies: Vec::new(),
restart_policy,
activation: ServiceActivationState::Enabled,
critical: true,
};
descriptor.validate()?;
Ok(descriptor)
}
pub fn with_dependency(self, dependency: impl Into<String>) -> SupervisorResult<Self> {
self.add_dependency(ServiceDependency::new(
dependency,
DependencyRequirement::DegradedAllowed,
)?)
}
pub fn with_dependency_requirement(
self,
dependency: impl Into<String>,
requirement: DependencyRequirement,
) -> SupervisorResult<Self> {
self.add_dependency(ServiceDependency::new(dependency, requirement)?)
}
fn add_dependency(mut self, dependency: ServiceDependency) -> SupervisorResult<Self> {
if dependency.service_id == self.name {
return Err(crate::SupervisorError::InvalidConfiguration(
"a service cannot depend on itself".to_string(),
));
}
if !self
.dependencies
.iter()
.any(|current| current.service_id == dependency.service_id)
{
self.dependencies.push(dependency);
self.dependencies
.sort_by(|left, right| left.service_id.cmp(&right.service_id));
}
Ok(self)
}
pub fn with_activation(mut self, activation: ServiceActivationState) -> Self {
self.activation = activation;
self
}
pub fn with_critical(mut self, critical: bool) -> Self {
self.critical = critical;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn resource(&self) -> ManagedResource {
self.resource
}
pub fn dependencies(&self) -> &[ServiceDependency] {
&self.dependencies
}
pub fn restart_policy(&self) -> RestartPolicy {
self.restart_policy
}
pub fn activation(&self) -> ServiceActivationState {
self.activation
}
pub fn is_critical(&self) -> bool {
self.critical
}
pub fn validate(&self) -> SupervisorResult<()> {
validate_name(&self.name)?;
self.restart_policy.validate()
}
}
pub trait ManagedService: Send + Sync {
fn descriptor(&self) -> &ServiceDescriptor;
fn start(&self) -> SupervisorResult<()>;
fn stop(&self, timeout: Duration) -> SupervisorResult<()>;
fn health(&self) -> ServiceHealth;
fn runtime_state(&self) -> ServiceRuntimeState {
match self.health() {
ServiceHealth::Starting => ServiceRuntimeState::Starting,
ServiceHealth::Ready | ServiceHealth::Healthy | ServiceHealth::Degraded => {
ServiceRuntimeState::Running
}
ServiceHealth::Stopping => ServiceRuntimeState::Stopping,
ServiceHealth::Failed => ServiceRuntimeState::Failed,
ServiceHealth::Unknown => ServiceRuntimeState::Stopped,
}
}
}
fn validate_name(name: &str) -> SupervisorResult<()> {
if name.is_empty()
|| name.len() > 128
|| !name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_'))
{
return Err(crate::SupervisorError::InvalidConfiguration(
"service name is invalid".to_string(),
));
}
Ok(())
}