use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use petgraph::algo::toposort;
use petgraph::graph::DiGraph;
use super::config::*;
use super::types::*;
impl DistributedQuantumOrchestrator {
pub fn new(config: DistributedOrchestratorConfig) -> Self {
Self {
config,
nodes: Arc::new(Mutex::new(HashMap::new())),
workflows: Arc::new(Mutex::new(HashMap::new())),
execution_counter: Arc::new(Mutex::new(0)),
}
}
pub fn execute_distributed(&self, circuit: &str) -> Result<DistributedExecutionResult, String> {
if circuit.trim().is_empty() {
return Err("execute_distributed: empty circuit description provided".to_string());
}
let nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let selected = nodes
.values()
.filter(|node| matches!(node.status, NodeStatus::Available))
.min_by_key(|node| node.node_id.clone());
let selected = match selected {
Some(node) => node.clone(),
None => {
return Err(format!(
"execute_distributed: no available nodes registered ({} nodes known, none Available)",
nodes.len()
));
}
};
drop(nodes);
let mut counter = self
.execution_counter
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*counter += 1;
let execution_id = format!("dist-exec-{:08}", *counter);
drop(counter);
Err(format!(
"execute_distributed[{execution_id}]: node '{}' selected and available, but network \
dispatch is not implemented in this build (requires a real inter-node transport); \
refusing to fabricate execution results",
selected.node_id
))
}
pub fn add_node(&mut self, node_info: NodeInfo) -> Result<(), String> {
if node_info.node_id.trim().is_empty() {
return Err("add_node: node_id must not be empty".to_string());
}
let mut nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
nodes.insert(node_info.node_id.clone(), node_info);
Ok(())
}
pub fn remove_node(&mut self, node_id: &str) -> Result<(), String> {
let mut nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match nodes.remove(node_id) {
Some(_) => Ok(()),
None => Err(format!("remove_node: node '{node_id}' is not registered")),
}
}
pub fn get_node_status(&self, node_id: &str) -> Result<NodeStatus, String> {
let nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
nodes
.get(node_id)
.map(|node| node.status.clone())
.ok_or_else(|| format!("get_node_status: node '{node_id}' is not registered"))
}
pub fn schedule_workflow(&self, workflow: DistributedWorkflow) -> Result<String, String> {
if workflow.steps.is_empty() {
return Err("schedule_workflow: workflow has no steps".to_string());
}
let mut graph = DiGraph::<&str, ()>::new();
let mut node_index = HashMap::new();
for step in &workflow.steps {
let idx = graph.add_node(step.as_str());
node_index.insert(step.as_str(), idx);
}
for (step, deps) in &workflow.dependencies {
let Some(&step_idx) = node_index.get(step.as_str()) else {
return Err(format!(
"schedule_workflow: dependency entry references unknown step '{step}'"
));
};
for dep in deps {
let Some(&dep_idx) = node_index.get(dep.as_str()) else {
return Err(format!(
"schedule_workflow: step '{step}' depends on unknown step '{dep}'"
));
};
graph.add_edge(dep_idx, step_idx, ());
}
}
if toposort(&graph, None).is_err() {
return Err(
"schedule_workflow: workflow dependency graph contains a cycle".to_string(),
);
}
let mut counter = self
.execution_counter
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*counter += 1;
let generated_id = if workflow.workflow_id.trim().is_empty() {
format!("workflow-{:08}", *counter)
} else {
workflow.workflow_id.clone()
};
drop(counter);
let mut workflows = self
.workflows
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
workflows.insert(generated_id.clone(), workflow);
Ok(generated_id)
}
pub fn node_count(&self) -> usize {
self.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
pub fn workflow_count(&self) -> usize {
self.workflows
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::SocketAddr;
fn make_node(id: &str, status: NodeStatus) -> NodeInfo {
NodeInfo {
node_id: id.to_string(),
address: "127.0.0.1:8000".parse::<SocketAddr>().unwrap(),
capabilities: NodeCapabilities::default(),
status,
last_heartbeat: Some(Instant::now()),
}
}
#[test]
fn test_orchestrator_real_node_registry() {
let mut orchestrator =
DistributedQuantumOrchestrator::new(DistributedOrchestratorConfig::default());
assert_eq!(orchestrator.node_count(), 0);
orchestrator
.add_node(make_node("node_a", NodeStatus::Available))
.unwrap();
assert_eq!(orchestrator.node_count(), 1);
assert!(matches!(
orchestrator.get_node_status("node_a").unwrap(),
NodeStatus::Available
));
let err = orchestrator.get_node_status("node_missing").unwrap_err();
assert!(err.contains("not registered"));
orchestrator.remove_node("node_a").unwrap();
assert_eq!(orchestrator.node_count(), 0);
assert!(orchestrator.remove_node("node_a").is_err());
}
#[test]
fn test_execute_distributed_honest_when_no_nodes() {
let orchestrator =
DistributedQuantumOrchestrator::new(DistributedOrchestratorConfig::default());
let result = orchestrator.execute_distributed("H 0; CNOT 0 1");
let err = result.unwrap_err();
assert!(err.contains("no available nodes"));
}
#[test]
fn test_execute_distributed_honest_error_no_fake_success() {
let mut orchestrator =
DistributedQuantumOrchestrator::new(DistributedOrchestratorConfig::default());
orchestrator
.add_node(make_node("node_a", NodeStatus::Available))
.unwrap();
let result = orchestrator.execute_distributed("H 0");
let err = result.unwrap_err();
assert!(err.contains("network"));
assert!(err.contains("node_a"));
}
#[test]
fn test_schedule_workflow_rejects_cycle() {
let orchestrator =
DistributedQuantumOrchestrator::new(DistributedOrchestratorConfig::default());
let mut dependencies = HashMap::new();
dependencies.insert("step_a".to_string(), vec!["step_b".to_string()]);
dependencies.insert("step_b".to_string(), vec!["step_a".to_string()]);
let workflow = DistributedWorkflow {
workflow_id: String::new(),
workflow_type: DistributedWorkflowType::Sequential,
steps: vec!["step_a".to_string(), "step_b".to_string()],
dependencies,
};
let err = orchestrator.schedule_workflow(workflow).unwrap_err();
assert!(err.contains("cycle"));
}
#[test]
fn test_schedule_workflow_accepts_valid_dag_and_generates_id() {
let orchestrator =
DistributedQuantumOrchestrator::new(DistributedOrchestratorConfig::default());
let mut dependencies = HashMap::new();
dependencies.insert("step_b".to_string(), vec!["step_a".to_string()]);
let workflow = DistributedWorkflow {
workflow_id: String::new(),
workflow_type: DistributedWorkflowType::Sequential,
steps: vec!["step_a".to_string(), "step_b".to_string()],
dependencies,
};
let id = orchestrator.schedule_workflow(workflow).unwrap();
assert!(id.starts_with("workflow-"));
assert_eq!(orchestrator.workflow_count(), 1);
}
}