use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::hash::Hash;
use core::time::Duration;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodusIdentitas {
pub alta: u64,
pub ima: u64,
}
impl NodusIdentitas {
pub const fn new(alta: u64, ima: u64) -> Self {
NodusIdentitas { alta, ima }
}
#[allow(clippy::cast_possible_truncation)]
pub const fn from_u128(value: u128) -> Self {
NodusIdentitas {
alta: (value >> 64) as u64,
ima: value as u64,
}
}
pub const fn to_u128(&self) -> u128 {
((self.alta as u128) << 64) | (self.ima as u128)
}
pub const NIL: Self = NodusIdentitas { alta: 0, ima: 0 };
pub const fn is_nil(&self) -> bool {
self.alta == 0 && self.ima == 0
}
#[cfg(feature = "std")]
pub fn generate() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
#[allow(clippy::cast_possible_truncation)] let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos() as u64);
NodusIdentitas {
alta: timestamp,
ima: COUNTER.fetch_add(1, Ordering::SeqCst),
}
}
}
impl fmt::Debug for NodusIdentitas {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NodusId({:016x}{:016x})", self.alta, self.ima)
}
}
impl fmt::Display for NodusIdentitas {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}{:016x}", self.alta, self.ima)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InscriptioNodi {
pub hospes: String,
pub portus: u16,
pub schema: ProtocollumSchema,
}
impl InscriptioNodi {
pub fn new(hospes: impl Into<String>, portus: u16) -> Self {
InscriptioNodi {
hospes: hospes.into(),
portus,
schema: ProtocollumSchema::Grpc,
}
}
pub fn with_schema(hospes: impl Into<String>, portus: u16, schema: ProtocollumSchema) -> Self {
InscriptioNodi {
hospes: hospes.into(),
portus,
schema,
}
}
pub fn to_uri(&self) -> String {
let schema = match self.schema {
ProtocollumSchema::Grpc => "grpc",
ProtocollumSchema::Http => "http",
ProtocollumSchema::Https => "https",
ProtocollumSchema::Tcp => "tcp",
ProtocollumSchema::Unix => "unix",
};
alloc::format!("{}://{}:{}", schema, self.hospes, self.portus)
}
}
impl fmt::Display for InscriptioNodi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.hospes, self.portus)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ProtocollumSchema {
#[default]
Grpc,
Http,
Https,
Tcp,
Unix,
}
#[derive(Debug, Clone)]
pub struct InformationesNodi {
pub identitas: NodusIdentitas,
pub inscriptio: InscriptioNodi,
pub munus: MunusNodi,
pub status: StatusNodi,
pub facultates: FacultatesNodi,
pub tituli: Vec<(String, String)>,
pub ultima_pulsatio: Option<Duration>,
}
impl InformationesNodi {
#[inline]
pub fn new(identitas: NodusIdentitas, inscriptio: InscriptioNodi) -> Self {
InformationesNodi {
identitas,
inscriptio,
munus: MunusNodi::Executor,
status: StatusNodi::Unknown,
facultates: FacultatesNodi::default(),
tituli: Vec::with_capacity(4),
ultima_pulsatio: None,
}
}
pub fn with_role(mut self, munus: MunusNodi) -> Self {
self.munus = munus;
self
}
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.tituli.push((key.into(), value.into()));
self
}
#[inline]
pub fn is_healthy(&self) -> bool {
matches!(self.status, StatusNodi::Sanus)
}
#[inline]
pub fn can_execute(&self) -> bool {
self.is_healthy()
&& matches!(
self.munus,
MunusNodi::Executor | MunusNodi::Coordinator | MunusNodi::All
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum MunusNodi {
Coordinator,
#[default]
Executor,
All,
Porta,
Repositorium,
}
impl fmt::Display for MunusNodi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MunusNodi::Coordinator => write!(f, "coordinator"),
MunusNodi::Executor => write!(f, "executor"),
MunusNodi::All => write!(f, "all"),
MunusNodi::Porta => write!(f, "gateway"),
MunusNodi::Repositorium => write!(f, "storage"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum StatusNodi {
#[default]
Unknown,
Sanus,
Surgit,
Descendit,
Aegrotus,
Inaccessibilis,
Exhaurit,
}
impl StatusNodi {
pub fn can_accept_work(&self) -> bool {
matches!(self, StatusNodi::Sanus)
}
}
impl fmt::Display for StatusNodi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StatusNodi::Unknown => write!(f, "unknown"),
StatusNodi::Sanus => write!(f, "healthy"),
StatusNodi::Surgit => write!(f, "starting"),
StatusNodi::Descendit => write!(f, "stopping"),
StatusNodi::Aegrotus => write!(f, "unhealthy"),
StatusNodi::Inaccessibilis => write!(f, "unreachable"),
StatusNodi::Exhaurit => write!(f, "draining"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FacultatesNodi {
pub nuclei_cpu: u32,
pub memoria_bytes: u64,
pub memoria_disponibilis: u64,
pub gpu: Option<GpuFacultas>,
pub effectus_tractati: Vec<u64>,
pub munera_maxima: u32,
pub munera_currentia: u32,
}
impl FacultatesNodi {
#[inline]
pub fn load_factor(&self) -> f64 {
if self.munera_maxima == 0 {
1.0
} else {
f64::from(self.munera_currentia) / f64::from(self.munera_maxima)
}
}
#[inline]
pub fn has_capacity(&self) -> bool {
self.munera_currentia < self.munera_maxima
}
#[inline]
pub fn can_handle_effect(&self, effect_id: u64) -> bool {
self.effectus_tractati.contains(&effect_id)
}
}
#[derive(Debug, Clone)]
pub struct GpuFacultas {
pub vendor: String,
pub modellum: String,
pub vram_bytes: u64,
pub versio_computationis: String,
}
#[derive(Debug, Clone, Default)]
pub enum AffinitasNodi {
#[default]
Quodlibet,
Localis,
Nodus(NodusIdentitas),
Tituli(Vec<(String, String)>),
Regio(String),
Facultates(RequirementaFacultatum),
Ponderata(Vec<(AffinitasNodi, u32)>),
Non(Box<AffinitasNodi>),
}
impl AffinitasNodi {
pub fn matches(&self, node: &InformationesNodi) -> bool {
match self {
AffinitasNodi::Quodlibet => true,
AffinitasNodi::Localis => false, AffinitasNodi::Nodus(id) => node.identitas == *id,
AffinitasNodi::Tituli(labels) => labels
.iter()
.all(|(k, v)| node.tituli.iter().any(|(nk, nv)| nk == k && nv == v)),
AffinitasNodi::Regio(region) => {
node.tituli.iter().any(|(k, v)| k == "regio" && v == region)
}
AffinitasNodi::Facultates(req) => req.satisfies(&node.facultates),
AffinitasNodi::Ponderata(affinities) => affinities.iter().any(|(a, _)| a.matches(node)),
AffinitasNodi::Non(inner) => !inner.matches(node),
}
}
pub fn score(&self, node: &InformationesNodi) -> u32 {
match self {
AffinitasNodi::Quodlibet => 1,
AffinitasNodi::Localis => 0, AffinitasNodi::Nodus(id) => {
if node.identitas == *id {
100
} else {
0
}
}
AffinitasNodi::Tituli(labels) => {
let matching = labels
.iter()
.filter(|(k, v)| node.tituli.iter().any(|(nk, nv)| nk == k && nv == v))
.count();
(matching * 10) as u32
}
AffinitasNodi::Regio(region) => {
if node.tituli.iter().any(|(k, v)| k == "regio" && v == region) {
50
} else {
0
}
}
AffinitasNodi::Facultates(req) => {
if req.satisfies(&node.facultates) {
30
} else {
0
}
}
AffinitasNodi::Ponderata(affinities) => affinities
.iter()
.map(|(a, weight)| a.score(node).saturating_mul(*weight))
.max()
.unwrap_or(0),
AffinitasNodi::Non(inner) => {
if inner.matches(node) {
0
} else {
10
}
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RequirementaFacultatum {
pub nuclei_cpu_min: Option<u32>,
pub memoria_min: Option<u64>,
pub gpu_requiritur: bool,
pub effectus_requiriti: Vec<u64>,
}
impl RequirementaFacultatum {
pub fn satisfies(&self, cap: &FacultatesNodi) -> bool {
if let Some(min_cpu) = self.nuclei_cpu_min
&& cap.nuclei_cpu < min_cpu
{
return false;
}
if let Some(min_mem) = self.memoria_min
&& cap.memoria_disponibilis < min_mem
{
return false;
}
if self.gpu_requiritur && cap.gpu.is_none() {
return false;
}
for effect_id in &self.effectus_requiriti {
if !cap.effectus_tractati.contains(effect_id) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_nodus_identitas() {
let id1 = NodusIdentitas::new(1, 2);
let id2 = NodusIdentitas::new(1, 2);
let id3 = NodusIdentitas::new(1, 3);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_nodus_identitas_u128() {
let value: u128 = 0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210;
let id = NodusIdentitas::from_u128(value);
assert_eq!(id.to_u128(), value);
}
#[test]
fn test_inscriptio_nodi() {
let addr = InscriptioNodi::new("localhost", 8080);
assert_eq!(addr.hospes, "localhost");
assert_eq!(addr.portus, 8080);
assert_eq!(addr.to_uri(), "grpc://localhost:8080");
}
#[test]
fn test_status_nodi() {
assert!(StatusNodi::Sanus.can_accept_work());
assert!(!StatusNodi::Aegrotus.can_accept_work());
assert!(!StatusNodi::Exhaurit.can_accept_work());
}
#[test]
fn test_facultates_load_factor() {
let cap = FacultatesNodi {
munera_maxima: 100,
munera_currentia: 50,
..Default::default()
};
assert!((cap.load_factor() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_affinitas_matches() {
let node = InformationesNodi {
identitas: NodusIdentitas::new(1, 1),
inscriptio: InscriptioNodi::new("localhost", 8080),
munus: MunusNodi::Executor,
status: StatusNodi::Sanus,
facultates: FacultatesNodi::default(),
tituli: vec![("regio".into(), "us-west".into())],
ultima_pulsatio: None,
};
assert!(AffinitasNodi::Quodlibet.matches(&node));
assert!(AffinitasNodi::Regio("us-west".into()).matches(&node));
assert!(!AffinitasNodi::Regio("us-east".into()).matches(&node));
}
}