use std::time::{Duration, Instant};
pub type NodeId = u64;
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum MeshOsEvent {
Tick,
ReplicaUpdate(ReplicaUpdate),
DaemonLifecycle {
daemon: DaemonRef,
signal: DaemonLifecycleSignal,
},
RttSample {
peer: NodeId,
rtt: Duration,
},
NodeHealth {
peer: NodeId,
health: NodeHealth,
},
AdminEvent(AdminEvent),
SignedIceCommit {
proposal: super::ice::IceActionProposal,
signatures: Vec<super::ice::OperatorSignature>,
issued_at_ms: u64,
blast_hash: super::ice::BlastRadiusHash,
},
SignedAdminCommit {
event: AdminEvent,
signature: super::ice::OperatorSignature,
issued_at_ms: u64,
},
LogLine(super::logs::LogLine),
BlobAnnouncement(BlobAnnouncement),
PlacementIntent(PlacementIntent),
DaemonIntentUpdate(DaemonIntentUpdate),
LocalReplicaIntent(LocalReplicaIntentUpdate),
ReplicaLeaderUpdate {
chain: ChainId,
leader: Option<NodeId>,
},
ReplicaLeaderLostAndRemoved {
chain: ChainId,
holder: NodeId,
},
ReplicaBecameHolderAndLeader {
chain: ChainId,
holder: NodeId,
},
MaintenanceTransitionObserved {
node: NodeId,
state: super::maintenance::MaintenanceState,
},
Shutdown,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct DaemonRef {
pub id: u64,
pub name: String,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum DaemonLifecycleSignal {
Started {
at: Instant,
},
ExitedCleanly {
at: Instant,
},
Crashed {
at: Instant,
reason: String,
},
HealthChanged {
at: Instant,
health: DaemonHealth,
},
SaturationChanged {
at: Instant,
saturation: f32,
},
}
pub use crate::adapter::net::compute::DaemonHealth;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum NodeHealth {
Healthy,
Degraded,
Unreachable,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ReplicaUpdate {
Added {
chain: ChainId,
holder: NodeId,
},
Removed {
chain: ChainId,
holder: NodeId,
},
Lost {
chain: ChainId,
holder: NodeId,
},
Repaired {
chain: ChainId,
holder: NodeId,
},
}
pub type ChainId = u64;
pub type MigrationId = u64;
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum AdminEvent {
EnterMaintenance {
node: NodeId,
drain_for: Option<Duration>,
},
ExitMaintenance {
node: NodeId,
},
Drain {
node: NodeId,
drain_for: Duration,
},
Cordon {
node: NodeId,
},
Uncordon {
node: NodeId,
},
RestartAllDaemons {
node: NodeId,
},
ClearAvoidList {
node: NodeId,
},
DropReplicas {
node: NodeId,
chains: Vec<ChainId>,
},
InvalidatePlacement {
node: NodeId,
},
FreezeCluster {
ttl: std::time::Duration,
},
ThawCluster,
FlushAvoidLists {
scope: AvoidScope,
},
ForceEvictReplica {
chain: ChainId,
victim: NodeId,
},
ForceRestartDaemon {
daemon: DaemonRef,
},
ForceCutover {
chain: ChainId,
target: NodeId,
},
KillMigration {
migration: MigrationId,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum AvoidScope {
Local {
node: NodeId,
},
OnPeer {
peer: NodeId,
},
Global,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlobAnnouncement {
pub blob: u64,
pub holder: NodeId,
pub size_bytes: u64,
pub added: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlacementIntent {
pub chain: ChainId,
pub desired_replicas: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DaemonIntent {
Run,
Stop,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DaemonIntentUpdate {
pub daemon: DaemonRef,
pub intent: DaemonIntent,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum LocalReplicaIntent {
Hold,
Drop,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LocalReplicaIntentUpdate {
pub chain: ChainId,
pub intent: LocalReplicaIntent,
}
impl AdminEvent {
pub fn is_ice(&self) -> bool {
matches!(
self,
AdminEvent::FreezeCluster { .. }
| AdminEvent::ThawCluster
| AdminEvent::FlushAvoidLists { .. }
| AdminEvent::ForceEvictReplica { .. }
| AdminEvent::ForceRestartDaemon { .. }
| AdminEvent::ForceCutover { .. }
| AdminEvent::KillMigration { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn admin_event_postcard_round_trips_every_variant() {
let cases = [
AdminEvent::EnterMaintenance {
node: 42,
drain_for: Some(Duration::from_secs(300)),
},
AdminEvent::EnterMaintenance {
node: 42,
drain_for: None,
},
AdminEvent::ExitMaintenance { node: 42 },
AdminEvent::Drain {
node: 42,
drain_for: Duration::from_secs(600),
},
AdminEvent::Cordon { node: 42 },
AdminEvent::Uncordon { node: 42 },
AdminEvent::RestartAllDaemons { node: 42 },
AdminEvent::ClearAvoidList { node: 42 },
AdminEvent::DropReplicas {
node: 42,
chains: vec![1, 2, 3],
},
AdminEvent::InvalidatePlacement { node: 42 },
AdminEvent::FreezeCluster {
ttl: Duration::from_secs(60),
},
AdminEvent::ThawCluster,
AdminEvent::FlushAvoidLists {
scope: AvoidScope::Local { node: 42 },
},
AdminEvent::FlushAvoidLists {
scope: AvoidScope::OnPeer { peer: 7 },
},
AdminEvent::FlushAvoidLists {
scope: AvoidScope::Global,
},
AdminEvent::ForceEvictReplica {
chain: 100,
victim: 7,
},
AdminEvent::ForceRestartDaemon {
daemon: DaemonRef {
id: 7,
name: "telemetry".into(),
},
},
AdminEvent::ForceCutover {
chain: 100,
target: 42,
},
AdminEvent::KillMigration { migration: 999 },
];
for ev in cases {
let bytes = postcard::to_allocvec(&ev).expect("encode");
let decoded: AdminEvent = postcard::from_bytes(&bytes).expect("decode");
assert_eq!(decoded, ev);
}
}
}