Files
wifi-densepose/vendor/ruvector/crates/prime-radiant/src/distributed/adapter.rs

389 lines
12 KiB
Rust

//! Adapter to ruvector-raft
//!
//! Wraps Raft consensus for coherence state replication.
use super::config::NodeRole;
use super::{DistributedCoherenceConfig, DistributedError, Result};
/// Command types for coherence state machine
#[derive(Debug, Clone)]
pub enum CoherenceCommand {
/// Update energy for an edge
UpdateEnergy { edge_id: (u64, u64), energy: f32 },
/// Set node state vector
SetNodeState { node_id: u64, state: Vec<f32> },
/// Record coherence checkpoint
Checkpoint { total_energy: f32, timestamp: u64 },
/// Mark region as incoherent
MarkIncoherent { region_id: u64, nodes: Vec<u64> },
/// Clear incoherence flag
ClearIncoherent { region_id: u64 },
}
impl CoherenceCommand {
/// Serialize command to bytes
pub fn to_bytes(&self) -> Vec<u8> {
// Simple serialization format
let mut bytes = Vec::new();
match self {
Self::UpdateEnergy { edge_id, energy } => {
bytes.push(0);
bytes.extend(edge_id.0.to_le_bytes());
bytes.extend(edge_id.1.to_le_bytes());
bytes.extend(energy.to_le_bytes());
}
Self::SetNodeState { node_id, state } => {
bytes.push(1);
bytes.extend(node_id.to_le_bytes());
bytes.extend((state.len() as u32).to_le_bytes());
for &v in state {
bytes.extend(v.to_le_bytes());
}
}
Self::Checkpoint {
total_energy,
timestamp,
} => {
bytes.push(2);
bytes.extend(total_energy.to_le_bytes());
bytes.extend(timestamp.to_le_bytes());
}
Self::MarkIncoherent { region_id, nodes } => {
bytes.push(3);
bytes.extend(region_id.to_le_bytes());
bytes.extend((nodes.len() as u32).to_le_bytes());
for &n in nodes {
bytes.extend(n.to_le_bytes());
}
}
Self::ClearIncoherent { region_id } => {
bytes.push(4);
bytes.extend(region_id.to_le_bytes());
}
}
bytes
}
/// Deserialize command from bytes
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.is_empty() {
return None;
}
let cmd_type = bytes[0];
let data = &bytes[1..];
match cmd_type {
0 if data.len() >= 20 => {
let src = u64::from_le_bytes(data[0..8].try_into().ok()?);
let dst = u64::from_le_bytes(data[8..16].try_into().ok()?);
let energy = f32::from_le_bytes(data[16..20].try_into().ok()?);
Some(Self::UpdateEnergy {
edge_id: (src, dst),
energy,
})
}
1 if data.len() >= 12 => {
let node_id = u64::from_le_bytes(data[0..8].try_into().ok()?);
let len = u32::from_le_bytes(data[8..12].try_into().ok()?) as usize;
if data.len() < 12 + len * 4 {
return None;
}
let state: Vec<f32> = (0..len)
.map(|i| {
let offset = 12 + i * 4;
f32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
})
.collect();
Some(Self::SetNodeState { node_id, state })
}
2 if data.len() >= 12 => {
let total_energy = f32::from_le_bytes(data[0..4].try_into().ok()?);
let timestamp = u64::from_le_bytes(data[4..12].try_into().ok()?);
Some(Self::Checkpoint {
total_energy,
timestamp,
})
}
3 if data.len() >= 12 => {
let region_id = u64::from_le_bytes(data[0..8].try_into().ok()?);
let len = u32::from_le_bytes(data[8..12].try_into().ok()?) as usize;
if data.len() < 12 + len * 8 {
return None;
}
let nodes: Vec<u64> = (0..len)
.map(|i| {
let offset = 12 + i * 8;
u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap())
})
.collect();
Some(Self::MarkIncoherent { region_id, nodes })
}
4 if data.len() >= 8 => {
let region_id = u64::from_le_bytes(data[0..8].try_into().ok()?);
Some(Self::ClearIncoherent { region_id })
}
_ => None,
}
}
}
/// Result of applying a command
#[derive(Debug, Clone)]
pub struct CommandResult {
/// Log index where command was applied
pub index: u64,
/// Term when command was applied
pub term: u64,
/// Whether command was successful
pub success: bool,
}
/// Adapter wrapping ruvector-raft for coherence coordination
#[derive(Debug)]
pub struct RaftAdapter {
/// Configuration
config: DistributedCoherenceConfig,
/// Current role (simulated without actual Raft)
role: NodeRole,
/// Current term
current_term: u64,
/// Current leader ID
current_leader: Option<String>,
/// Log index
log_index: u64,
/// Pending commands (for simulation)
pending_commands: Vec<CoherenceCommand>,
}
impl RaftAdapter {
/// Create a new Raft adapter
pub fn new(config: DistributedCoherenceConfig) -> Self {
let is_leader = config.is_single_node();
Self {
role: if is_leader {
NodeRole::Leader
} else {
NodeRole::Follower
},
current_term: 1,
current_leader: if is_leader {
Some(config.node_id.clone())
} else {
None
},
log_index: 0,
pending_commands: Vec::new(),
config,
}
}
/// Get current role
pub fn role(&self) -> NodeRole {
self.role
}
/// Get current term
pub fn current_term(&self) -> u64 {
self.current_term
}
/// Get current leader
pub fn current_leader(&self) -> Option<&str> {
self.current_leader.as_deref()
}
/// Check if this node is the leader
pub fn is_leader(&self) -> bool {
self.role.is_leader()
}
/// Submit a command for replication
pub fn submit_command(&mut self, command: CoherenceCommand) -> Result<CommandResult> {
if !self.is_leader() {
return Err(DistributedError::NotLeader {
leader: self.current_leader.clone(),
});
}
// In a real implementation, this would go through Raft
self.log_index += 1;
self.pending_commands.push(command);
Ok(CommandResult {
index: self.log_index,
term: self.current_term,
success: true,
})
}
/// Update energy for an edge
pub fn update_energy(&mut self, edge_id: (u64, u64), energy: f32) -> Result<CommandResult> {
let command = CoherenceCommand::UpdateEnergy { edge_id, energy };
self.submit_command(command)
}
/// Set node state
pub fn set_node_state(&mut self, node_id: u64, state: Vec<f32>) -> Result<CommandResult> {
let command = CoherenceCommand::SetNodeState { node_id, state };
self.submit_command(command)
}
/// Record checkpoint
pub fn checkpoint(&mut self, total_energy: f32, timestamp: u64) -> Result<CommandResult> {
let command = CoherenceCommand::Checkpoint {
total_energy,
timestamp,
};
self.submit_command(command)
}
/// Mark region as incoherent
pub fn mark_incoherent(&mut self, region_id: u64, nodes: Vec<u64>) -> Result<CommandResult> {
let command = CoherenceCommand::MarkIncoherent { region_id, nodes };
self.submit_command(command)
}
/// Clear incoherence flag
pub fn clear_incoherent(&mut self, region_id: u64) -> Result<CommandResult> {
let command = CoherenceCommand::ClearIncoherent { region_id };
self.submit_command(command)
}
/// Get pending commands (for state machine application)
pub fn take_pending_commands(&mut self) -> Vec<CoherenceCommand> {
std::mem::take(&mut self.pending_commands)
}
/// Simulate leader election (for testing)
pub fn become_leader(&mut self) {
self.role = NodeRole::Leader;
self.current_term += 1;
self.current_leader = Some(self.config.node_id.clone());
}
/// Simulate stepping down
pub fn step_down(&mut self) {
self.role = NodeRole::Follower;
self.current_leader = None;
}
/// Get cluster status
pub fn cluster_status(&self) -> ClusterStatus {
ClusterStatus {
node_id: self.config.node_id.clone(),
role: self.role,
term: self.current_term,
leader: self.current_leader.clone(),
cluster_size: self.config.cluster_members.len(),
quorum_size: self.config.quorum_size(),
log_index: self.log_index,
}
}
}
/// Status of the Raft cluster
#[derive(Debug, Clone)]
pub struct ClusterStatus {
/// This node's ID
pub node_id: String,
/// Current role
pub role: NodeRole,
/// Current term
pub term: u64,
/// Current leader (if known)
pub leader: Option<String>,
/// Total cluster size
pub cluster_size: usize,
/// Quorum size
pub quorum_size: usize,
/// Current log index
pub log_index: u64,
}
impl ClusterStatus {
/// Check if cluster is healthy (has leader)
pub fn is_healthy(&self) -> bool {
self.leader.is_some()
}
/// Check if this node can accept writes
pub fn can_write(&self) -> bool {
self.role.is_leader()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adapter_creation() {
let config = DistributedCoherenceConfig::single_node("node1");
let adapter = RaftAdapter::new(config);
assert!(adapter.is_leader());
assert_eq!(adapter.current_term(), 1);
}
#[test]
fn test_command_serialization() {
let cmd = CoherenceCommand::UpdateEnergy {
edge_id: (1, 2),
energy: 0.5,
};
let bytes = cmd.to_bytes();
let recovered = CoherenceCommand::from_bytes(&bytes).unwrap();
if let CoherenceCommand::UpdateEnergy { edge_id, energy } = recovered {
assert_eq!(edge_id, (1, 2));
assert!((energy - 0.5).abs() < 1e-6);
} else {
panic!("Wrong command type");
}
}
#[test]
fn test_submit_command() {
let config = DistributedCoherenceConfig::single_node("node1");
let mut adapter = RaftAdapter::new(config);
let result = adapter.update_energy((1, 2), 0.5).unwrap();
assert!(result.success);
assert_eq!(result.index, 1);
let pending = adapter.take_pending_commands();
assert_eq!(pending.len(), 1);
}
#[test]
fn test_not_leader_error() {
let config = DistributedCoherenceConfig {
node_id: "node1".to_string(),
cluster_members: vec![
"node1".to_string(),
"node2".to_string(),
"node3".to_string(),
],
..Default::default()
};
let mut adapter = RaftAdapter::new(config);
adapter.step_down();
let result = adapter.update_energy((1, 2), 0.5);
assert!(result.is_err());
}
#[test]
fn test_cluster_status() {
let config = DistributedCoherenceConfig::single_node("node1");
let adapter = RaftAdapter::new(config);
let status = adapter.cluster_status();
assert!(status.is_healthy());
assert!(status.can_write());
assert_eq!(status.cluster_size, 1);
}
}