use std::collections::{BTreeMap, HashMap};
use crate::cluster::config::{NodeClass, NodeIdentity};
#[derive(Debug, Clone)]
pub struct NodeInfo {
pub identity: NodeIdentity,
pub class: NodeClass,
pub metadata: HashMap<String, String>,
pub running_actors: Vec<String>,
pub is_alive: bool,
}
impl NodeInfo {
pub fn new(
identity: NodeIdentity,
class: NodeClass,
metadata: HashMap<String, String>,
) -> Self {
Self {
identity,
class,
metadata,
running_actors: Vec::new(),
is_alive: true,
}
}
pub fn node_id(&self) -> String {
self.identity.node_id_string()
}
pub fn actor_count(&self) -> usize {
self.running_actors.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct ClusterView {
pub nodes: BTreeMap<String, NodeInfo>,
actor_locations: HashMap<String, String>,
}
impl ClusterView {
pub fn new() -> Self {
Self::default()
}
pub fn upsert_node(&mut self, info: NodeInfo) {
self.nodes.insert(info.node_id(), info);
}
pub fn mark_failed(&mut self, node_id: &str) {
if let Some(node) = self.nodes.get_mut(node_id) {
node.is_alive = false;
}
}
pub fn mark_alive(&mut self, node_id: &str) {
if let Some(node) = self.nodes.get_mut(node_id) {
node.is_alive = true;
}
}
pub fn remove_node(&mut self, node_id: &str) -> Option<NodeInfo> {
if let Some(node) = self.nodes.remove(node_id) {
for label in &node.running_actors {
self.actor_locations.remove(label);
}
Some(node)
} else {
None
}
}
pub fn add_actor(&mut self, node_id: &str, label: &str) {
if let Some(node) = self.nodes.get_mut(node_id)
&& !node.running_actors.contains(&label.to_string())
{
node.running_actors.push(label.to_string());
self.actor_locations
.insert(label.to_string(), node_id.to_string());
}
}
pub fn remove_actor(&mut self, node_id: &str, label: &str) {
if let Some(node) = self.nodes.get_mut(node_id) {
node.running_actors.retain(|l| l != label);
}
self.actor_locations.remove(label);
}
pub fn remove_actor_anywhere(&mut self, label: &str) -> Option<String> {
if let Some(node_id) = self.actor_locations.remove(label) {
if let Some(node) = self.nodes.get_mut(&node_id) {
node.running_actors.retain(|l| l != label);
}
Some(node_id)
} else {
None
}
}
pub fn find_actor(&self, label: &str) -> Option<&str> {
self.actor_locations.get(label).map(|s| s.as_str())
}
pub fn alive_nodes(&self) -> impl Iterator<Item = &NodeInfo> {
self.nodes.values().filter(|n| n.is_alive)
}
pub fn alive_count(&self) -> usize {
self.alive_nodes().count()
}
pub fn total_count(&self) -> usize {
self.nodes.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_identity(name: &str, incarnation: u64) -> NodeIdentity {
NodeIdentity::for_test(name, incarnation)
}
#[test]
fn test_cluster_view_upsert_and_query() {
let mut view = ClusterView::new();
let node = NodeInfo::new(test_identity("alpha", 1), NodeClass::Worker, HashMap::new());
let node_id = node.node_id();
view.upsert_node(node);
assert_eq!(view.alive_count(), 1);
assert_eq!(view.total_count(), 1);
assert!(view.nodes.get(&node_id).unwrap().is_alive);
}
#[test]
fn test_cluster_view_mark_failed() {
let mut view = ClusterView::new();
let node = NodeInfo::new(test_identity("alpha", 1), NodeClass::Worker, HashMap::new());
let node_id = node.node_id();
view.upsert_node(node);
view.mark_failed(&node_id);
assert_eq!(view.alive_count(), 0);
assert_eq!(view.total_count(), 1);
assert!(!view.nodes.get(&node_id).unwrap().is_alive);
}
#[test]
fn test_cluster_view_actor_tracking() {
let mut view = ClusterView::new();
let node = NodeInfo::new(test_identity("alpha", 1), NodeClass::Worker, HashMap::new());
let node_id = node.node_id();
view.upsert_node(node);
view.add_actor(&node_id, "worker/0");
view.add_actor(&node_id, "worker/1");
assert_eq!(view.find_actor("worker/0"), Some(node_id.as_str()));
assert_eq!(view.nodes.get(&node_id).unwrap().actor_count(), 2);
view.remove_actor(&node_id, "worker/0");
assert_eq!(view.find_actor("worker/0"), None);
assert_eq!(view.nodes.get(&node_id).unwrap().actor_count(), 1);
}
#[test]
fn test_cluster_view_remove_actor_anywhere() {
let mut view = ClusterView::new();
let n1 = NodeInfo::new(test_identity("alpha", 1), NodeClass::Worker, HashMap::new());
let n2 = NodeInfo::new(test_identity("beta", 2), NodeClass::Worker, HashMap::new());
let n1_id = n1.node_id();
let n2_id = n2.node_id();
view.upsert_node(n1);
view.upsert_node(n2);
view.add_actor(&n2_id, "worker/0");
let removed_from = view.remove_actor_anywhere("worker/0");
assert_eq!(removed_from.as_deref(), Some(n2_id.as_str()));
assert_eq!(view.find_actor("worker/0"), None);
let _ = n1_id; assert_eq!(view.remove_actor_anywhere("nonexistent"), None);
}
}