use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AtpdRegionId(u64);
impl AtpdRegionId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AtpdChildRole {
IdentityManager,
PeerDirectory,
PathManager,
ReceiveService,
TransferSupervisor,
CacheSeeder,
InboxMailbox,
DiagnosticsEndpoint,
RelayService,
RendezvousService,
}
impl AtpdChildRole {
#[must_use]
pub const fn service_name(self) -> &'static str {
match self {
Self::IdentityManager => "identity_manager",
Self::PeerDirectory => "peer_directory",
Self::PathManager => "path_manager",
Self::ReceiveService => "receive_service",
Self::TransferSupervisor => "transfer_supervisor",
Self::CacheSeeder => "cache_seeder",
Self::InboxMailbox => "inbox_mailbox",
Self::DiagnosticsEndpoint => "diagnostics_endpoint",
Self::RelayService => "relay_service",
Self::RendezvousService => "rendezvous_service",
}
}
#[must_use]
pub const fn is_optional(self) -> bool {
matches!(self, Self::RelayService | Self::RendezvousService)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtpdRestartPolicy {
CriticalEscalate,
Restart {
max_restarts: u8,
window_secs: u64,
},
DisableOptional,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtpdNameLease {
pub name: &'static str,
pub root_scoped: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtpdStopAction {
StopChild,
DrainTransfers {
require_resume_state: bool,
},
ReleaseNameLease,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtpdChildSpec {
pub role: AtpdChildRole,
pub region: AtpdRegionId,
pub parent_region: AtpdRegionId,
pub depends_on: Vec<AtpdChildRole>,
pub restart: AtpdRestartPolicy,
pub lease: AtpdNameLease,
pub stop_actions: Vec<AtpdStopAction>,
}
impl AtpdChildSpec {
#[must_use]
pub fn new(
role: AtpdChildRole,
region: AtpdRegionId,
parent_region: AtpdRegionId,
depends_on: Vec<AtpdChildRole>,
restart: AtpdRestartPolicy,
stop_actions: Vec<AtpdStopAction>,
) -> Self {
Self {
role,
region,
parent_region,
depends_on,
restart,
lease: AtpdNameLease {
name: role.service_name(),
root_scoped: true,
},
stop_actions,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtpdTopologyError {
DuplicateRole(AtpdChildRole),
DuplicateRegion(AtpdRegionId),
DetachedChild {
role: AtpdChildRole,
parent: AtpdRegionId,
expected: AtpdRegionId,
},
MissingDependency {
role: AtpdChildRole,
dependency: AtpdChildRole,
},
MissingRequiredRole(AtpdChildRole),
DependencyCycle,
TransferDrainMissing,
}
impl fmt::Display for AtpdTopologyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateRole(role) => write!(f, "duplicate atpd role {role:?}"),
Self::DuplicateRegion(region) => write!(f, "duplicate atpd region {}", region.get()),
Self::DetachedChild {
role,
parent,
expected,
} => write!(
f,
"atpd child {role:?} is parented by {}, expected {}",
parent.get(),
expected.get()
),
Self::MissingDependency { role, dependency } => {
write!(f, "atpd child {role:?} depends on missing {dependency:?}")
}
Self::MissingRequiredRole(role) => write!(f, "missing required atpd role {role:?}"),
Self::DependencyCycle => f.write_str("atpd dependency cycle"),
Self::TransferDrainMissing => {
f.write_str("transfer supervisor must drain or persist resume state")
}
}
}
}
impl std::error::Error for AtpdTopologyError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtpdTopology {
pub root_region: AtpdRegionId,
pub children: Vec<AtpdChildSpec>,
}
impl AtpdTopology {
pub const REQUIRED_ROLES: [AtpdChildRole; 8] = [
AtpdChildRole::IdentityManager,
AtpdChildRole::PeerDirectory,
AtpdChildRole::PathManager,
AtpdChildRole::ReceiveService,
AtpdChildRole::TransferSupervisor,
AtpdChildRole::CacheSeeder,
AtpdChildRole::InboxMailbox,
AtpdChildRole::DiagnosticsEndpoint,
];
pub fn validate(&self) -> Result<(), AtpdTopologyError> {
let mut roles = BTreeSet::new();
let mut regions = BTreeSet::new();
for child in &self.children {
if !roles.insert(child.role) {
return Err(AtpdTopologyError::DuplicateRole(child.role));
}
if !regions.insert(child.region) {
return Err(AtpdTopologyError::DuplicateRegion(child.region));
}
if child.parent_region != self.root_region {
return Err(AtpdTopologyError::DetachedChild {
role: child.role,
parent: child.parent_region,
expected: self.root_region,
});
}
}
for required in Self::REQUIRED_ROLES {
if !roles.contains(&required) {
return Err(AtpdTopologyError::MissingRequiredRole(required));
}
}
for child in &self.children {
for dependency in &child.depends_on {
if !roles.contains(dependency) {
return Err(AtpdTopologyError::MissingDependency {
role: child.role,
dependency: *dependency,
});
}
}
}
let transfer = self
.children
.iter()
.find(|child| child.role == AtpdChildRole::TransferSupervisor)
.expect("required role already checked");
if !transfer
.stop_actions
.iter()
.any(|action| matches!(action, AtpdStopAction::DrainTransfers { .. }))
{
return Err(AtpdTopologyError::TransferDrainMissing);
}
self.start_order().map(|_| ())
}
pub fn start_order(&self) -> Result<Vec<AtpdChildRole>, AtpdTopologyError> {
let children: BTreeMap<AtpdChildRole, &AtpdChildSpec> = self
.children
.iter()
.map(|child| (child.role, child))
.collect();
let mut started = BTreeSet::new();
let mut order = Vec::with_capacity(self.children.len());
while order.len() < self.children.len() {
let before = order.len();
for child in &self.children {
if started.contains(&child.role) {
continue;
}
if child
.depends_on
.iter()
.all(|dependency| started.contains(dependency))
{
if !children.contains_key(&child.role) {
return Err(AtpdTopologyError::MissingRequiredRole(child.role));
}
started.insert(child.role);
order.push(child.role);
}
}
if before == order.len() {
return Err(AtpdTopologyError::DependencyCycle);
}
}
Ok(order)
}
pub fn stop_order(&self) -> Result<Vec<AtpdChildRole>, AtpdTopologyError> {
let mut order = self.start_order()?;
order.reverse();
Ok(order)
}
#[must_use]
pub fn no_detached_children(&self) -> bool {
self.children
.iter()
.all(|child| child.parent_region == self.root_region)
}
}