Skip to main content

oxirs_cluster/
lib.rs

1//! # OxiRS Cluster
2//!
3//! [![Version](https://img.shields.io/badge/version-0.4.1-blue)](https://github.com/cool-japan/oxirs/releases)
4//! [![docs.rs](https://docs.rs/oxirs-cluster/badge.svg)](https://docs.rs/oxirs-cluster)
5//!
6//! **Status**: Production Release (v0.4.1)
7//! **Stability**: Public APIs are stable. Production-ready with comprehensive testing.
8//!
9//! Raft-backed distributed dataset for high availability and horizontal scaling.
10//!
11//! This crate provides distributed storage capabilities using Raft consensus with
12//! multi-region support, Byzantine fault tolerance, and advanced replication strategies.
13//!
14//! ## Features
15//!
16//! - **Raft Consensus**: Production-ready Raft implementation using openraft
17//! - **Distributed RDF Storage**: Scalable, consistent RDF triple storage
18//! - **Automatic Failover**: Leader election and automatic recovery
19//! - **Node Discovery**: Multiple discovery mechanisms (static, DNS, multicast)
20//! - **Replication Management**: Configurable replication strategies
21//! - **SPARQL Support**: Distributed SPARQL query execution
22//! - **Transaction Support**: Distributed ACID transactions
23//!
24//! ## Example
25//!
26//! ```ignore
27//! use oxirs_cluster::{ClusterNode, NodeConfig};
28//! use std::net::SocketAddr;
29//!
30//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
31//!     let config = NodeConfig {
32//!         node_id: 1,
33//!         address: "127.0.0.1:8080".parse()?,
34//!         data_dir: "./data".to_string(),
35//!         peers: vec![2, 3],
36//!     };
37//!
38//!     let mut node = ClusterNode::new(config).await?;
39//!     node.start().await?;
40//!
41//!     // Insert data through consensus
42//!     node.insert_triple(
43//!         "<http://example.org/subject>",
44//!         "<http://example.org/predicate>",
45//!         "\"object\"")
46//!     .await?;
47//!
48//!     Ok(())
49//! }
50//! ```
51
52#![allow(clippy::field_reassign_with_default)]
53#![allow(clippy::single_match)]
54#![allow(clippy::collapsible_if)]
55#![allow(clippy::clone_on_copy)]
56#![allow(clippy::type_complexity)]
57#![allow(clippy::collapsible_match)]
58#![allow(clippy::manual_clamp)]
59#![allow(clippy::needless_range_loop)]
60#![allow(clippy::or_fun_call)]
61#![allow(clippy::if_same_then_else)]
62#![allow(clippy::only_used_in_recursion)]
63#![allow(clippy::new_without_default)]
64#![allow(clippy::derivable_impls)]
65#![allow(clippy::useless_conversion)]
66use serde::{Deserialize, Serialize};
67use std::collections::HashMap;
68use std::net::SocketAddr;
69use std::sync::Arc;
70use tokio::sync::RwLock;
71
72pub mod adaptive_leader_election;
73pub mod advanced_partitioning;
74pub mod advanced_storage;
75pub mod alerting;
76pub mod auto_scaling;
77pub mod backup_restore;
78pub mod circuit_breaker;
79pub mod cloud_integration;
80pub mod cluster_metrics;
81pub mod cluster_metrics_manager;
82pub mod cluster_metrics_stats;
83#[cfg(test)]
84mod cluster_metrics_tests;
85pub mod cluster_metrics_types;
86pub mod compression_strategy;
87pub mod conflict_resolution;
88pub mod consensus;
89pub mod crash_recovery;
90pub mod data_rebalancing;
91pub mod disaster_recovery;
92pub mod discovery;
93pub mod distributed_query;
94pub mod distributed_tracing;
95pub mod edge_computing;
96pub mod encryption;
97pub mod enhanced_node_discovery;
98pub mod enhanced_snapshotting;
99pub mod error;
100pub mod failover;
101pub mod federation;
102pub mod gpu_acceleration;
103pub mod health_monitor;
104pub mod health_monitoring;
105pub mod memory_optimization;
106pub mod merkle_tree;
107pub mod ml_optimization;
108pub mod multi_tenant;
109pub mod mvcc;
110pub mod mvcc_storage;
111pub mod network;
112pub mod neural_architecture_search;
113pub mod node_lifecycle;
114pub mod node_status_tracker;
115pub mod operational_transformation;
116pub mod optimization;
117pub mod partition_detection;
118pub mod performance_metrics;
119pub mod performance_monitor;
120pub mod raft;
121#[cfg(feature = "raft")]
122pub mod raft_durable;
123#[cfg(feature = "raft")]
124mod raft_network;
125pub mod raft_optimization;
126pub mod raft_profiling;
127pub mod raft_state;
128pub mod range_partitioning;
129pub mod read_replica;
130pub mod region_manager;
131pub mod replication;
132pub mod replication_lag_monitor;
133pub mod rl_consensus_optimizer;
134pub mod rolling_upgrade;
135pub mod rolling_upgrade_orchestrator;
136pub mod split_brain_detector;
137pub mod visualization_dashboard;
138pub mod zero_downtime_migration;
139// Temporarily disabled due to missing scirs2_core features
140// pub mod revolutionary_cluster_optimization;
141pub mod cross_dc;
142pub mod network_compression;
143pub mod security;
144pub mod serialization;
145pub mod shard;
146pub mod shard_manager;
147pub mod shard_migration;
148pub mod shard_routing;
149pub mod split_brain_prevention;
150pub mod storage;
151pub mod strong_consistency;
152pub mod tls;
153pub mod topology;
154pub mod transaction;
155pub mod transaction_optimizer;
156
157#[cfg(feature = "bft")]
158pub mod bft;
159#[cfg(feature = "bft")]
160pub mod bft_consensus;
161#[cfg(feature = "bft")]
162pub mod bft_network;
163
164pub mod gossip_scaling;
165pub mod sla_manager;
166pub mod stream_integration;
167
168// New modules added in v0.2.0
169pub mod adaptive_consistent_hash;
170pub mod cross_dc_consistency;
171pub mod distributed_tx_coordinator;
172
173// v1.1.0 Consistent hashing with virtual nodes and bounded loads
174pub mod vnodes_hash_ring;
175
176// v1.2.0 Gossip protocol for cluster membership management
177pub mod membership_gossip;
178
179// v1.2.0 Bully algorithm leader election simulation
180pub mod leader_election;
181
182// v1.2.0 Raft snapshot management with retention and checksum validation
183pub mod snapshot_manager;
184
185// v1.5.0 Consistent-hash shard router
186pub mod consistent_shard_router;
187
188// v1.6.0 Partition rebalancing for cluster data redistribution
189pub mod partition_rebalancer;
190
191// v1.7.0 Cluster node health monitoring with heartbeats
192pub mod node_monitor;
193
194// v1.8.0 Automatic failover handling with split-brain prevention
195pub mod failover_manager;
196
197/// Anti-entropy protocol for distributed consistency (v1.9.0).
198///
199/// Includes [`anti_entropy::AntiEntropyThrottle`] — a tokio-Semaphore-backed
200/// concurrency gate that limits concurrent sync sessions to
201/// `max_concurrent_syncs` (default 4) per node.
202pub mod anti_entropy;
203
204/// Gossip protocol primitives — fanout policies (`GossipFanout`) for
205/// epidemic-protocol dissemination.  For adaptive fanout (log₂-based,
206/// loss-rate aware) see [`gossip_scaling`].
207pub mod gossip;
208
209/// In-memory cluster simulation for scalability testing (10 → 1000 nodes).
210///
211/// `SimCluster` runs N virtual nodes in a single tokio runtime with bounded
212/// `mpsc` mailboxes — no real network sockets.  Available unconditionally so
213/// integration tests in `tests/` can import it without the `simulation`
214/// feature flag.  The `simulation` feature remains available for downstream
215/// crates that want to opt in explicitly.
216pub mod simulation;
217
218/// Replication bandwidth throttling: token-bucket per-peer rate limiting with adaptive adjustment (v2.0.0).
219pub mod replication_throttle;
220
221/// Data migration between cluster nodes: plan creation, range-based transfer,
222/// checksum-validated chunks, migration lifecycle and statistics (v1.1.0 round 14)
223pub mod data_migrator;
224
225/// Consistent-hash shard routing for distributed cluster nodes (v1.1.0 round 15)
226pub mod shard_router;
227
228/// Raft-style election timer: randomised timeout, TimerState (Idle/Running/Expired),
229/// reset/check/stop lifecycle, LCG seed for deterministic tests (v1.1.0 round 16)
230pub mod election_timer;
231
232/// Advanced compression codec system: Compressor trait, IdentityCodec, RleCodec,
233/// Lz4Codec, ZstdCodec, CodecRegistry (v0.3.0 W2-S8).
234pub mod compression;
235
236/// Advanced backup policy DSL: BackupPolicy, RetentionTier, GfsRotation,
237/// BackupExecutor with audit log, DestinationConfig (v0.3.0 W2-S8).
238pub mod backup;
239
240/// Per-node SLA admission control on Raft log writes and read replicas.
241///
242/// Reuses the shared per-tenant SLA primitives from [`oxirs_core::sla`]
243/// (W2-S4): [`oxirs_core::sla::SlaClass`],
244/// [`oxirs_core::sla::AdmissionController`], [`oxirs_core::sla::SlaThresholds`].
245/// Adds cluster-aware wrappers: [`sla::ClusterAdmissionController`],
246/// [`sla::SlaProposerGate`], [`sla::SlaReaderGate`] (v0.3.0 W2-S5).
247pub mod sla;
248
249/// Real-time streaming integration with the Raft log (W3-S9).
250///
251/// Provides [`streaming::ClusterSink`], a [`streaming::StreamSink`]
252/// implementation that proposes streaming events through
253/// [`consensus::ConsensusManager`] using the existing
254/// [`raft::RdfCommand`] payload, plus a
255/// [`streaming::BackpressureBridge`] that upstream operators poll for
256/// flow-control signals.
257pub mod streaming;
258
259/// Hierarchical log-replication topology for O(log N) fan-out (Phase B, v0.3.0).
260///
261/// Replaces O(N) all-to-all log shipping with a √N-relay spanning tree that
262/// respects rack/AZ/region topology. The leader ships to R = ceil(√N) relays,
263/// each relay fans out to its AZ members: total messages per round ≈ 2·√N.
264///
265/// See [`log_replication_topology::ReplicationTopology`] for the main entry point.
266pub mod log_replication_topology;
267
268/// Witness nodes for quorum participation without full log storage (Phase B, v0.3.0).
269///
270/// A witness participates in Raft leader election and confirms recent
271/// `AppendEntries` RPCs, but stores only the last `tail_window` log entries.
272/// For a 1000-node cluster with 200 witnesses, disk usage drops by ~20%.
273///
274/// See [`witness_node::WitnessNode`] for the main entry point.
275pub mod witness_node;
276
277/// Real-TCP-network E2E cluster harness (Phase C, v0.3.0).
278///
279/// Proves gossip and replication primitives over actual TCP sockets
280/// (localhost, single-process, multiple tokio tasks).  Not a production
281/// cluster; designed for integration testing.
282///
283/// See [`tcp_cluster::TcpClusterNetwork`] for the main entry point.
284pub mod tcp_cluster;
285
286/// Comprehensive certification suite for operational correctness guarantees (v1.0.0 LTS).
287///
288/// Validates four correctness dimensions via deterministic in-memory simulation:
289/// consistency guarantees, network-partition resilience, Raft invariants, and
290/// SLA latency bounds.
291///
292/// See [`certification::CertificationSuite`] for the main entry point.
293pub mod certification;
294
295pub use log_replication_topology::{
296    NodeDescriptor, ReplicationRole, ReplicationTopology, TopologyNode,
297};
298pub use tcp_cluster::{
299    ClusterMessage, GossipState, MessageCodec, NetworkStats, TcpClusterNetwork, TcpClusterNode,
300    TcpNodeConfig, TcpNodeError,
301};
302pub use witness_node::{
303    VoteRequest, VoteResponse, WitnessAppendRequest, WitnessAppendResponse, WitnessLogEntry,
304    WitnessNode,
305};
306
307pub use error::{ClusterError, Result};
308pub use failover::{FailoverConfig, FailoverManager, FailoverStrategy, RecoveryAction};
309pub use health_monitor::{HealthMonitor, HealthMonitorConfig, NodeHealth, SystemMetrics};
310
311// Temporarily disabled - Re-export revolutionary cluster optimization types
312// pub use revolutionary_cluster_optimization::{
313//     RevolutionaryClusterOptimizer, RevolutionaryClusterConfig, ConsensusOptimizationConfig,
314//     DataDistributionConfig, AdaptiveReplicationConfig, NetworkOptimizationConfig,
315//     ClusterPerformanceTargets, ClusterOptimizationResult, ClusterState, NodeState,
316//     ClusterOptimizationContext, ClusterAnalytics, ScalingPrediction,
317//     RevolutionaryClusterOptimizerFactory, ConsensusOptimizationStrategy,
318//     DataDistributionStrategy, AdaptiveReplicationStrategy, NetworkOptimizationStrategy,
319// };
320
321use conflict_resolution::{
322    ConflictResolver, ResolutionStrategy, TimestampedOperation, VectorClock,
323};
324use consensus::ConsensusManager;
325use discovery::{DiscoveryConfig, DiscoveryService, NodeInfo};
326use distributed_query::{DistributedQueryExecutor, ResultBinding};
327use edge_computing::{EdgeComputingManager, EdgeDeploymentStrategy, EdgeDeviceProfile};
328use raft::{OxirsNodeId, RdfResponse};
329use region_manager::{
330    ConsensusStrategy as RegionConsensusStrategy, MultiRegionReplicationStrategy, Region,
331    RegionManager,
332};
333use replication::{ReplicationManager, ReplicationStats, ReplicationStrategy};
334
335/// Multi-region deployment configuration
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct MultiRegionConfig {
338    /// Region identifier where this node is located
339    pub region_id: String,
340    /// Availability zone identifier
341    pub availability_zone_id: String,
342    /// Data center identifier (optional)
343    pub data_center: Option<String>,
344    /// Rack identifier (optional)
345    pub rack: Option<String>,
346    /// List of all regions in the deployment
347    pub regions: Vec<Region>,
348    /// Consensus strategy for multi-region operations
349    pub consensus_strategy: RegionConsensusStrategy,
350    /// Replication strategy for multi-region
351    pub replication_strategy: MultiRegionReplicationStrategy,
352    /// Conflict resolution strategy for distributed operations
353    pub conflict_resolution_strategy: ResolutionStrategy,
354    /// Edge computing configuration
355    pub edge_config: Option<EdgeComputingConfig>,
356    /// Enable advanced monitoring and metrics
357    pub enable_monitoring: bool,
358}
359
360/// Edge computing configuration
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct EdgeComputingConfig {
363    /// Enable edge computing features
364    pub enabled: bool,
365    /// Local edge device profile
366    pub device_profile: EdgeDeviceProfile,
367    /// Edge deployment strategy
368    pub deployment_strategy: EdgeDeploymentStrategy,
369    /// Enable intelligent caching
370    pub enable_intelligent_caching: bool,
371    /// Enable network condition monitoring
372    pub enable_network_monitoring: bool,
373}
374
375/// Cluster node configuration
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct NodeConfig {
378    /// Unique node identifier
379    pub node_id: OxirsNodeId,
380    /// Network address for communication
381    pub address: SocketAddr,
382    /// Data directory for persistent storage
383    pub data_dir: String,
384    /// List of peer node IDs
385    pub peers: Vec<OxirsNodeId>,
386    /// Known network addresses of entries in `peers`, keyed by node id.
387    ///
388    /// Required (for every id in `peers`) to form a real multi-node Raft
389    /// cluster — `ClusterNode::new` feeds this straight into
390    /// `RaftNode::set_network` via `ConsensusManager::with_raft_network`, so
391    /// the Raft transport (`raft_network.rs`) knows where to dial each peer
392    /// and what address to advertise for itself. Left empty for a genuine
393    /// single-node deployment (`peers` empty), which never needs it.
394    pub peer_addresses: HashMap<OxirsNodeId, SocketAddr>,
395    /// Discovery configuration
396    pub discovery: Option<DiscoveryConfig>,
397    /// Replication strategy
398    pub replication_strategy: Option<ReplicationStrategy>,
399    /// Use Byzantine fault tolerance instead of Raft.
400    ///
401    /// This field is always present so a caller can request BFT regardless of
402    /// how the crate was compiled; if it is `true` but the `bft` feature was
403    /// not enabled, [`ClusterNode::start`] fails loud rather than silently
404    /// running Raft.
405    pub use_bft: bool,
406    /// Multi-region deployment configuration
407    pub region_config: Option<MultiRegionConfig>,
408}
409
410impl NodeConfig {
411    /// Create a new node configuration
412    pub fn new(node_id: OxirsNodeId, address: SocketAddr) -> Self {
413        Self {
414            node_id,
415            address,
416            data_dir: format!("./data/node-{node_id}"),
417            peers: Vec::new(),
418            peer_addresses: HashMap::new(),
419            discovery: Some(DiscoveryConfig::default()),
420            replication_strategy: Some(ReplicationStrategy::default()),
421            use_bft: false,
422            region_config: None,
423        }
424    }
425
426    /// Add a peer to the configuration
427    pub fn add_peer(&mut self, peer_id: OxirsNodeId) -> &mut Self {
428        if !self.peers.contains(&peer_id) && peer_id != self.node_id {
429            self.peers.push(peer_id);
430        }
431        self
432    }
433
434    /// Record (or update) the network address of a peer, so a multi-node
435    /// Raft cluster can be formed. Does not implicitly add `peer_id` to
436    /// `peers` — call `add_peer` too if it isn't already listed.
437    pub fn add_peer_address(&mut self, peer_id: OxirsNodeId, address: SocketAddr) -> &mut Self {
438        self.peer_addresses.insert(peer_id, address);
439        self
440    }
441
442    /// Set the discovery configuration
443    pub fn with_discovery(mut self, discovery: DiscoveryConfig) -> Self {
444        self.discovery = Some(discovery);
445        self
446    }
447
448    /// Set the replication strategy
449    pub fn with_replication_strategy(mut self, strategy: ReplicationStrategy) -> Self {
450        self.replication_strategy = Some(strategy);
451        self
452    }
453
454    /// Enable Byzantine fault tolerance.
455    ///
456    /// Requesting BFT here always compiles; it only takes effect when the crate
457    /// is built with the `bft` feature. Otherwise [`ClusterNode::start`] returns
458    /// an explicit configuration error.
459    pub fn with_bft(mut self, enable: bool) -> Self {
460        self.use_bft = enable;
461        self
462    }
463
464    /// Set multi-region configuration
465    pub fn with_multi_region(mut self, region_config: MultiRegionConfig) -> Self {
466        self.region_config = Some(region_config);
467        self
468    }
469
470    /// Check if multi-region is enabled
471    pub fn is_multi_region_enabled(&self) -> bool {
472        self.region_config.is_some()
473    }
474
475    /// Get region ID if configured
476    pub fn region_id(&self) -> Option<&str> {
477        self.region_config
478            .as_ref()
479            .map(|config| config.region_id.as_str())
480    }
481
482    /// Get availability zone ID if configured
483    pub fn availability_zone_id(&self) -> Option<&str> {
484        self.region_config
485            .as_ref()
486            .map(|config| config.availability_zone_id.as_str())
487    }
488}
489
490/// Cluster node implementation
491pub struct ClusterNode {
492    config: NodeConfig,
493    consensus: ConsensusManager,
494    discovery: DiscoveryService,
495    /// The node's replication manager, shared so the background maintenance
496    /// task operates on the *real* manager (with the node's actual replicas)
497    /// rather than a throwaway instance holding no state.
498    replication: Arc<RwLock<ReplicationManager>>,
499    query_executor: DistributedQueryExecutor,
500    region_manager: Option<Arc<RegionManager>>,
501    conflict_resolver: Arc<ConflictResolver>,
502    #[allow(dead_code)]
503    edge_manager: Option<Arc<EdgeComputingManager>>,
504    local_vector_clock: Arc<RwLock<VectorClock>>,
505    running: Arc<RwLock<bool>>,
506    byzantine_mode: Arc<RwLock<bool>>,
507    network_isolated: Arc<RwLock<bool>>,
508    /// Byzantine fault-tolerant consensus manager, present only when the node
509    /// was started with `use_bft = true` and the `bft` feature is enabled.
510    /// Held so its background tasks keep running for the node's lifetime.
511    #[cfg(feature = "bft")]
512    bft_manager: Option<Arc<bft_consensus::BftConsensusManager>>,
513}
514
515impl ClusterNode {
516    /// Create a new cluster node
517    pub async fn new(config: NodeConfig) -> Result<Self> {
518        // Validate configuration
519        if config.data_dir.is_empty() {
520            return Err(ClusterError::Config(
521                "Data directory cannot be empty".to_string(),
522            ));
523        }
524
525        // Create data directory if it doesn't exist
526        tokio::fs::create_dir_all(&config.data_dir)
527            .await
528            .map_err(|e| ClusterError::Other(format!("Failed to create data directory: {e}")))?;
529
530        // Initialize consensus manager. `with_raft_network` feeds this
531        // node's own address and its peers' known addresses down to
532        // `RaftNode::set_network`, so `ConsensusManager::init()` (called
533        // from `ClusterNode::start`) can construct a real multi-node Raft
534        // instance rather than failing with `NetworkNotConfigured` (only a
535        // genuine single-node peer set works without it).
536        #[cfg(feature = "raft")]
537        let consensus = ConsensusManager::new(config.node_id, config.peers.clone())
538            .with_raft_network(config.address, config.peer_addresses.clone())
539            .with_raft_storage_dir(std::path::PathBuf::from(&config.data_dir));
540        #[cfg(not(feature = "raft"))]
541        let consensus = ConsensusManager::new(config.node_id, config.peers.clone());
542
543        // Initialize discovery service
544        let discovery_config = config.discovery.clone().unwrap_or_default();
545        let discovery = DiscoveryService::new(config.node_id, config.address, discovery_config);
546
547        // Initialize replication manager
548        let replication_strategy = config.replication_strategy.clone().unwrap_or_default();
549        let replication = Arc::new(RwLock::new(ReplicationManager::new(
550            replication_strategy,
551            config.node_id,
552        )));
553
554        // Initialize distributed query executor
555        let query_executor = DistributedQueryExecutor::new(config.node_id);
556
557        // Initialize conflict resolver
558        let default_resolution_strategy = if let Some(region_config) = &config.region_config {
559            region_config.conflict_resolution_strategy.clone()
560        } else {
561            ResolutionStrategy::LastWriterWins
562        };
563        let conflict_resolver = Arc::new(ConflictResolver::new(default_resolution_strategy));
564
565        // Initialize vector clock
566        let mut vector_clock = VectorClock::new();
567        vector_clock.increment(config.node_id);
568        let local_vector_clock = Arc::new(RwLock::new(vector_clock));
569
570        // Initialize region manager if multi-region is configured
571        let region_manager = if let Some(region_config) = &config.region_config {
572            let manager = Arc::new(RegionManager::new(
573                region_config.region_id.clone(),
574                region_config.availability_zone_id.clone(),
575                region_config.consensus_strategy.clone(),
576                region_config.replication_strategy.clone(),
577            ));
578
579            // Initialize with region topology
580            manager
581                .initialize(region_config.regions.clone())
582                .await
583                .map_err(|e| {
584                    ClusterError::Other(format!("Failed to initialize region manager: {e}"))
585                })?;
586
587            // Register this node in the region manager
588            manager
589                .register_node(
590                    config.node_id,
591                    region_config.region_id.clone(),
592                    region_config.availability_zone_id.clone(),
593                    region_config.data_center.clone(),
594                    region_config.rack.clone(),
595                )
596                .await
597                .map_err(|e| {
598                    ClusterError::Other(format!("Failed to register node in region manager: {e}"))
599                })?;
600
601            Some(manager)
602        } else {
603            None
604        };
605
606        // Initialize edge computing manager if configured
607        let edge_manager = if let Some(region_config) = &config.region_config {
608            if let Some(edge_config) = &region_config.edge_config {
609                if edge_config.enabled {
610                    let manager = Arc::new(EdgeComputingManager::new());
611
612                    // Register this device with the edge manager
613                    manager
614                        .register_device(edge_config.device_profile.clone())
615                        .await
616                        .map_err(|e| {
617                            ClusterError::Other(format!("Failed to register edge device: {e}"))
618                        })?;
619
620                    Some(manager)
621                } else {
622                    None
623                }
624            } else {
625                None
626            }
627        } else {
628            None
629        };
630
631        Ok(Self {
632            config,
633            consensus,
634            discovery,
635            replication,
636            query_executor,
637            region_manager,
638            conflict_resolver,
639            edge_manager,
640            local_vector_clock,
641            running: Arc::new(RwLock::new(false)),
642            byzantine_mode: Arc::new(RwLock::new(false)),
643            network_isolated: Arc::new(RwLock::new(false)),
644            #[cfg(feature = "bft")]
645            bft_manager: None,
646        })
647    }
648
649    /// Access the Byzantine fault-tolerant consensus manager, if this node was
650    /// started with `use_bft = true` and the `bft` feature is enabled.
651    #[cfg(feature = "bft")]
652    pub fn bft_manager(&self) -> Option<&Arc<bft_consensus::BftConsensusManager>> {
653        self.bft_manager.as_ref()
654    }
655
656    /// Construct and start the Byzantine fault-tolerant consensus manager for
657    /// this node, binding it to a persistent state-machine backend rooted at the
658    /// node's data directory. Stores the manager so its background tasks stay
659    /// alive for the node's lifetime.
660    #[cfg(feature = "bft")]
661    async fn start_bft_consensus(&mut self) -> Result<()> {
662        use crate::bft_consensus::BftConsensusManager;
663        use crate::network::NetworkConfig;
664        use crate::storage::{PersistentStorage, StorageConfig};
665
666        let storage_config = StorageConfig {
667            data_dir: self.config.data_dir.clone(),
668            ..StorageConfig::default()
669        };
670        let storage = Arc::new(
671            PersistentStorage::new(self.config.node_id, storage_config)
672                .await
673                .map_err(|e| {
674                    ClusterError::Storage(format!("failed to open BFT storage backend: {e}"))
675                })?,
676        );
677
678        let peers: Vec<String> = self.config.peers.iter().map(|p| p.to_string()).collect();
679        let manager = BftConsensusManager::new(
680            self.config.node_id.to_string(),
681            peers,
682            storage,
683            NetworkConfig::default(),
684        )
685        .await?;
686        manager.start().await?;
687
688        self.bft_manager = Some(Arc::new(manager));
689        Ok(())
690    }
691
692    /// Start the cluster node
693    pub async fn start(&mut self) -> Result<()> {
694        {
695            let mut running = self.running.write().await;
696            if *running {
697                return Ok(());
698            }
699            *running = true;
700        }
701
702        // Byzantine fault-tolerant consensus is opt-in via `NodeConfig::use_bft`
703        // and replaces the Raft path. When requested but the crate was built
704        // without the `bft` feature, fail loud rather than silently running Raft
705        // under a caller who explicitly asked for BFT.
706        if self.config.use_bft {
707            #[cfg(feature = "bft")]
708            {
709                self.start_bft_consensus().await?;
710                tracing::info!(
711                    "Cluster node {} started in Byzantine fault-tolerant mode",
712                    self.config.node_id
713                );
714                return Ok(());
715            }
716            #[cfg(not(feature = "bft"))]
717            {
718                let mut running = self.running.write().await;
719                *running = false;
720                return Err(ClusterError::Config(format!(
721                    "node {} requested Byzantine fault tolerance (use_bft = true) but this build \
722                     was compiled without the 'bft' feature; refusing to silently fall back to \
723                     Raft",
724                    self.config.node_id
725                )));
726            }
727        }
728
729        tracing::info!(
730            "Starting cluster node {} at {} with {} peers",
731            self.config.node_id,
732            self.config.address,
733            self.config.peers.len()
734        );
735
736        // Start discovery service
737        self.discovery
738            .start()
739            .await
740            .map_err(|e| ClusterError::Other(format!("Failed to start discovery service: {e}")))?;
741
742        // Discover initial nodes
743        let discovered_nodes = self
744            .discovery
745            .discover_nodes()
746            .await
747            .map_err(|e| ClusterError::Other(format!("Failed to discover nodes: {e}")))?;
748
749        // Add discovered nodes to replication manager and query executor
750        for node in discovered_nodes {
751            if node.node_id != self.config.node_id {
752                self.replication
753                    .write()
754                    .await
755                    .add_replica(node.node_id, node.address.to_string());
756                self.query_executor.add_node(node.node_id).await;
757            }
758        }
759
760        // Initialize consensus system
761        self.consensus
762            .init()
763            .await
764            .map_err(|e| ClusterError::Other(format!("Failed to initialize consensus: {e}")))?;
765
766        tracing::info!("Cluster node {} started successfully", self.config.node_id);
767
768        // Start background tasks
769        self.start_background_tasks().await;
770
771        Ok(())
772    }
773
774    /// Stop the cluster node.
775    ///
776    /// Models a real node crash/stop (not a graceful departure): this
777    /// node's Raft participation is torn down abruptly via
778    /// `ConsensusManager::stop_raft` (no leadership transfer — contrast
779    /// `graceful_shutdown`), so peers stop hearing from it and a healthy
780    /// remaining majority can elect a new leader if it was one. A later
781    /// `start()` call rebuilds and rejoins Raft from scratch.
782    pub async fn stop(&mut self) -> Result<()> {
783        let mut running = self.running.write().await;
784        if !*running {
785            return Ok(());
786        }
787
788        tracing::info!("Stopping cluster node {}", self.config.node_id);
789
790        // Stop discovery service
791        self.discovery
792            .stop()
793            .await
794            .map_err(|e| ClusterError::Other(format!("Failed to stop discovery service: {e}")))?;
795
796        // Abruptly stop Raft participation so peers actually notice this
797        // node is gone (see doc comment above) and free the Raft RPC
798        // listener's port for a later `start()` to rebind.
799        self.consensus
800            .stop_raft()
801            .await
802            .map_err(|e| ClusterError::Other(format!("Failed to stop consensus: {e}")))?;
803
804        *running = false;
805
806        tracing::info!("Cluster node {} stopped", self.config.node_id);
807
808        Ok(())
809    }
810
811    /// Check if this node is the leader
812    pub async fn is_leader(&self) -> bool {
813        self.consensus.is_leader().await
814    }
815
816    /// Get current consensus term
817    pub async fn current_term(&self) -> u64 {
818        self.consensus.current_term().await
819    }
820
821    /// Insert a triple through distributed consensus
822    pub async fn insert_triple(
823        &self,
824        subject: &str,
825        predicate: &str,
826        object: &str,
827    ) -> Result<RdfResponse> {
828        if !self.is_leader().await {
829            return Err(ClusterError::NotLeader);
830        }
831
832        let response = self
833            .consensus
834            .insert_triple(
835                subject.to_string(),
836                predicate.to_string(),
837                object.to_string(),
838            )
839            .await?;
840
841        Ok(response)
842    }
843
844    /// Delete a triple through distributed consensus
845    pub async fn delete_triple(
846        &self,
847        subject: &str,
848        predicate: &str,
849        object: &str,
850    ) -> Result<RdfResponse> {
851        if !self.is_leader().await {
852            return Err(ClusterError::NotLeader);
853        }
854
855        let response = self
856            .consensus
857            .delete_triple(
858                subject.to_string(),
859                predicate.to_string(),
860                object.to_string(),
861            )
862            .await?;
863
864        Ok(response)
865    }
866
867    /// Clear all triples through distributed consensus
868    pub async fn clear_store(&self) -> Result<RdfResponse> {
869        if !self.is_leader().await {
870            return Err(ClusterError::NotLeader);
871        }
872
873        let response = self.consensus.clear_store().await?;
874        Ok(response)
875    }
876
877    /// Begin a distributed transaction
878    pub async fn begin_transaction(&self) -> Result<String> {
879        if !self.is_leader().await {
880            return Err(ClusterError::NotLeader);
881        }
882
883        let tx_id = uuid::Uuid::new_v4().to_string();
884        let _response = self.consensus.begin_transaction(tx_id.clone()).await?;
885
886        Ok(tx_id)
887    }
888
889    /// Commit a distributed transaction
890    pub async fn commit_transaction(&self, tx_id: &str) -> Result<RdfResponse> {
891        if !self.is_leader().await {
892            return Err(ClusterError::NotLeader);
893        }
894
895        let response = self.consensus.commit_transaction(tx_id.to_string()).await?;
896        Ok(response)
897    }
898
899    /// Rollback a distributed transaction
900    pub async fn rollback_transaction(&self, tx_id: &str) -> Result<RdfResponse> {
901        if !self.is_leader().await {
902            return Err(ClusterError::NotLeader);
903        }
904
905        let response = self
906            .consensus
907            .rollback_transaction(tx_id.to_string())
908            .await?;
909        Ok(response)
910    }
911
912    /// Query triples (can be done on any node)
913    pub async fn query_triples(
914        &self,
915        subject: Option<&str>,
916        predicate: Option<&str>,
917        object: Option<&str>,
918    ) -> Vec<(String, String, String)> {
919        self.consensus.query(subject, predicate, object).await
920    }
921
922    /// Execute SPARQL query using distributed query processing
923    pub async fn query_sparql(&self, sparql: &str) -> Result<Vec<String>> {
924        let bindings = self
925            .query_executor
926            .execute_query(sparql)
927            .await
928            .map_err(|e| ClusterError::Other(format!("Query execution failed: {e}")))?;
929
930        // Convert result bindings to string format
931        let results = bindings
932            .into_iter()
933            .map(|binding| {
934                let vars: Vec<String> = binding
935                    .variables
936                    .into_iter()
937                    .map(|(var, val)| format!("{var}: {val}"))
938                    .collect();
939                vars.join(", ")
940            })
941            .collect();
942
943        Ok(results)
944    }
945
946    /// Execute SPARQL query and return structured results
947    pub async fn query_sparql_bindings(&self, sparql: &str) -> Result<Vec<ResultBinding>> {
948        self.query_executor
949            .execute_query(sparql)
950            .await
951            .map_err(|e| ClusterError::Other(format!("Query execution failed: {e}")))
952    }
953
954    /// Get query execution statistics
955    pub async fn get_query_statistics(
956        &self,
957    ) -> Result<std::collections::HashMap<String, distributed_query::QueryStats>> {
958        Ok(self.query_executor.get_statistics().await)
959    }
960
961    /// Clear query cache
962    pub async fn clear_query_cache(&self) -> Result<()> {
963        self.query_executor.clear_cache().await;
964        Ok(())
965    }
966
967    /// Get the number of triples in the store
968    pub async fn len(&self) -> usize {
969        self.consensus.len().await
970    }
971
972    /// Check if the store is empty
973    pub async fn is_empty(&self) -> bool {
974        self.consensus.is_empty().await
975    }
976
977    /// Add a new node to the cluster
978    pub async fn add_cluster_node(
979        &mut self,
980        node_id: OxirsNodeId,
981        address: SocketAddr,
982    ) -> Result<()> {
983        if node_id == self.config.node_id {
984            return Err(ClusterError::Config(
985                "Cannot add self to cluster".to_string(),
986            ));
987        }
988
989        // Add to configuration
990        self.config.add_peer(node_id);
991
992        // Add to discovery
993        let node_info = NodeInfo::new(node_id, address);
994        self.discovery.add_node(node_info);
995
996        // Add to replication
997        self.replication
998            .write()
999            .await
1000            .add_replica(node_id, address.to_string());
1001
1002        // Add to query executor
1003        self.query_executor.add_node(node_id).await;
1004
1005        // Add to consensus (this would trigger Raft membership change)
1006        self.consensus.add_peer(node_id);
1007
1008        tracing::info!("Added node {} at {} to cluster", node_id, address);
1009
1010        Ok(())
1011    }
1012
1013    /// Remove a node from the cluster
1014    pub async fn remove_cluster_node(&mut self, node_id: OxirsNodeId) -> Result<()> {
1015        if node_id == self.config.node_id {
1016            return Err(ClusterError::Config(
1017                "Cannot remove self from cluster".to_string(),
1018            ));
1019        }
1020
1021        // Remove from configuration
1022        self.config.peers.retain(|&id| id != node_id);
1023
1024        // Remove from discovery
1025        self.discovery.remove_node(node_id);
1026
1027        // Remove from replication
1028        self.replication.write().await.remove_replica(node_id);
1029
1030        // Remove from query executor
1031        self.query_executor.remove_node(node_id).await;
1032
1033        // Remove from consensus (this would trigger Raft membership change)
1034        self.consensus.remove_peer(node_id);
1035
1036        tracing::info!("Removed node {} from cluster", node_id);
1037
1038        Ok(())
1039    }
1040
1041    /// Get comprehensive cluster status
1042    pub async fn get_status(&self) -> ClusterStatus {
1043        let consensus_status = self.consensus.get_status().await;
1044        let discovery_stats = self.discovery.get_stats().clone();
1045        let replication_stats = self.replication.read().await.get_stats().clone();
1046
1047        // Get region status if multi-region is enabled
1048        let region_status = if let Some(region_manager) = &self.region_manager {
1049            let region_id = region_manager.get_local_region().to_string();
1050            let availability_zone_id = region_manager.get_local_availability_zone().to_string();
1051            let regional_peers = region_manager.get_nodes_in_region(&region_id).await;
1052            let topology = region_manager.get_topology().await;
1053            let monitoring_active = region_manager.is_monitoring_active().await;
1054
1055            Some(RegionStatus {
1056                region_id,
1057                availability_zone_id,
1058                regional_peer_count: regional_peers.len(),
1059                total_regions: topology.regions.len(),
1060                monitoring_active,
1061            })
1062        } else {
1063            None
1064        };
1065
1066        ClusterStatus {
1067            node_id: self.config.node_id,
1068            address: self.config.address,
1069            is_leader: consensus_status.is_leader,
1070            current_term: consensus_status.current_term,
1071            peer_count: consensus_status.peer_count,
1072            triple_count: consensus_status.triple_count,
1073            discovery_stats,
1074            replication_stats,
1075            is_running: *self.running.read().await,
1076            region_status,
1077        }
1078    }
1079
1080    /// Start background maintenance tasks
1081    async fn start_background_tasks(&mut self) {
1082        let running = Arc::clone(&self.running);
1083
1084        // Discovery and health check task
1085        let discovery_config = self.config.discovery.clone().unwrap_or_default();
1086        let mut discovery_clone =
1087            DiscoveryService::new(self.config.node_id, self.config.address, discovery_config);
1088
1089        tokio::spawn(async move {
1090            while *running.read().await {
1091                discovery_clone.run_periodic_tasks().await;
1092                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
1093            }
1094        });
1095
1096        // Replication maintenance task.
1097        //
1098        // Runs against the node's REAL, shared replication manager (so it sees
1099        // the node's actual replicas), and re-checks the `running` flag every
1100        // iteration so `stop()`/`graceful_shutdown()` actually terminates it —
1101        // instead of the previous throwaway `with_raft_consensus(...)` instance
1102        // (empty state) driven by an infinite loop that never observed
1103        // shutdown (a permanent task leak).
1104        let replication = Arc::clone(&self.replication);
1105        let running_clone = Arc::clone(&self.running);
1106
1107        tokio::spawn(async move {
1108            const HEALTH_CHECK_INTERVAL: std::time::Duration = std::time::Duration::from_secs(30);
1109            const STALE_THRESHOLD: std::time::Duration = std::time::Duration::from_secs(60);
1110
1111            while *running_clone.read().await {
1112                tokio::time::sleep(HEALTH_CHECK_INTERVAL).await;
1113                if !*running_clone.read().await {
1114                    break;
1115                }
1116                replication
1117                    .write()
1118                    .await
1119                    .maintenance_tick(STALE_THRESHOLD)
1120                    .await;
1121            }
1122        });
1123    }
1124
1125    /// Add a new node to the cluster using consensus protocol
1126    pub async fn add_node_with_consensus(
1127        &mut self,
1128        node_id: OxirsNodeId,
1129        address: SocketAddr,
1130    ) -> Result<()> {
1131        self.consensus
1132            .add_node_with_consensus(node_id, address.to_string())
1133            .await
1134            .map_err(|e| {
1135                ClusterError::Other(format!("Failed to add node through consensus: {e}"))
1136            })?;
1137
1138        // Update local configuration
1139        self.config.add_peer(node_id);
1140
1141        // Add to discovery, replication, and query executor
1142        let node_info = NodeInfo::new(node_id, address);
1143        self.discovery.add_node(node_info);
1144        self.replication
1145            .write()
1146            .await
1147            .add_replica(node_id, address.to_string());
1148        self.query_executor.add_node(node_id).await;
1149
1150        Ok(())
1151    }
1152
1153    /// Remove a node from the cluster using consensus protocol
1154    pub async fn remove_node_with_consensus(&mut self, node_id: OxirsNodeId) -> Result<()> {
1155        self.consensus
1156            .remove_node_with_consensus(node_id)
1157            .await
1158            .map_err(|e| {
1159                ClusterError::Other(format!("Failed to remove node through consensus: {e}"))
1160            })?;
1161
1162        // Update local configuration
1163        self.config.peers.retain(|&id| id != node_id);
1164
1165        // Remove from discovery, replication, and query executor
1166        self.discovery.remove_node(node_id);
1167        self.replication.write().await.remove_replica(node_id);
1168        self.query_executor.remove_node(node_id).await;
1169
1170        Ok(())
1171    }
1172
1173    /// Gracefully shutdown this node
1174    pub async fn graceful_shutdown(&mut self) -> Result<()> {
1175        tracing::info!(
1176            "Initiating graceful shutdown of cluster node {}",
1177            self.config.node_id
1178        );
1179
1180        // Stop background tasks first
1181        {
1182            let mut running = self.running.write().await;
1183            *running = false;
1184        }
1185
1186        // Gracefully shutdown consensus layer (includes leadership transfer if needed)
1187        self.consensus
1188            .graceful_shutdown()
1189            .await
1190            .map_err(|e| ClusterError::Other(format!("Failed to shutdown consensus: {e}")))?;
1191
1192        // Stop discovery and replication services
1193        self.discovery
1194            .stop()
1195            .await
1196            .map_err(|e| ClusterError::Other(format!("Failed to stop discovery: {e}")))?;
1197
1198        tracing::info!("Cluster node {} gracefully shutdown", self.config.node_id);
1199        Ok(())
1200    }
1201
1202    /// Transfer leadership to another node
1203    pub async fn transfer_leadership(&mut self, target_node: OxirsNodeId) -> Result<()> {
1204        if !self.config.peers.contains(&target_node) {
1205            return Err(ClusterError::Config(format!(
1206                "Target node {target_node} not in cluster"
1207            )));
1208        }
1209
1210        self.consensus
1211            .transfer_leadership(target_node)
1212            .await
1213            .map_err(|e| ClusterError::Other(format!("Failed to transfer leadership: {e}")))?;
1214
1215        Ok(())
1216    }
1217
1218    /// Force evict a non-responsive node
1219    pub async fn force_evict_node(&mut self, node_id: OxirsNodeId) -> Result<()> {
1220        self.consensus
1221            .force_evict_node(node_id)
1222            .await
1223            .map_err(|e| ClusterError::Other(format!("Failed to force evict node: {e}")))?;
1224
1225        // Update local configuration
1226        self.config.peers.retain(|&id| id != node_id);
1227        self.discovery.remove_node(node_id);
1228        self.replication.write().await.remove_replica(node_id);
1229        self.query_executor.remove_node(node_id).await;
1230
1231        Ok(())
1232    }
1233
1234    /// Check health of all peer nodes
1235    pub async fn check_cluster_health(&self) -> Result<Vec<consensus::NodeHealthStatus>> {
1236        self.consensus
1237            .check_peer_health()
1238            .await
1239            .map_err(|e| ClusterError::Other(format!("Failed to check cluster health: {e}")))
1240    }
1241
1242    /// Attempt recovery from partition or failure
1243    pub async fn attempt_recovery(&mut self) -> Result<()> {
1244        self.consensus
1245            .attempt_recovery()
1246            .await
1247            .map_err(|e| ClusterError::Other(format!("Failed to recover cluster: {e}")))?;
1248
1249        tracing::info!(
1250            "Cluster recovery completed for node {}",
1251            self.config.node_id
1252        );
1253        Ok(())
1254    }
1255
1256    /// Get the node ID
1257    pub fn id(&self) -> OxirsNodeId {
1258        self.config.node_id
1259    }
1260
1261    /// Count triples in the store
1262    pub async fn count_triples(&self) -> Result<usize> {
1263        Ok(self.len().await)
1264    }
1265
1266    /// Check if the node is active (running and not isolated)
1267    pub async fn is_active(&self) -> Result<bool> {
1268        Ok(*self.running.read().await && !*self.network_isolated.read().await)
1269    }
1270
1271    /// Isolate the node from network (simulate network partition)
1272    pub async fn isolate_network(&self) -> Result<()> {
1273        let mut isolated = self.network_isolated.write().await;
1274        *isolated = true;
1275        tracing::info!("Node {} network isolated", self.config.node_id);
1276        Ok(())
1277    }
1278
1279    /// Restore network connectivity
1280    pub async fn restore_network(&self) -> Result<()> {
1281        let mut isolated = self.network_isolated.write().await;
1282        *isolated = false;
1283        tracing::info!("Node {} network restored", self.config.node_id);
1284        Ok(())
1285    }
1286
1287    /// Enable Byzantine behavior (for testing)
1288    pub async fn enable_byzantine_mode(&self) -> Result<()> {
1289        let mut byzantine = self.byzantine_mode.write().await;
1290        *byzantine = true;
1291        tracing::info!("Node {} Byzantine mode enabled", self.config.node_id);
1292        Ok(())
1293    }
1294
1295    /// Check if node is in Byzantine mode
1296    pub async fn is_byzantine(&self) -> Result<bool> {
1297        Ok(*self.byzantine_mode.read().await)
1298    }
1299
1300    /// Get multi-region manager (if configured)
1301    pub fn region_manager(&self) -> Option<&Arc<RegionManager>> {
1302        self.region_manager.as_ref()
1303    }
1304
1305    /// Check if multi-region deployment is enabled
1306    pub fn is_multi_region_enabled(&self) -> bool {
1307        self.region_manager.is_some()
1308    }
1309
1310    /// Get current node's region ID
1311    pub fn get_region_id(&self) -> Option<String> {
1312        self.region_manager
1313            .as_ref()
1314            .map(|rm| rm.get_local_region().to_string())
1315    }
1316
1317    /// Get current node's availability zone ID
1318    pub fn get_availability_zone_id(&self) -> Option<String> {
1319        self.region_manager
1320            .as_ref()
1321            .map(|rm| rm.get_local_availability_zone().to_string())
1322    }
1323
1324    /// Get nodes in the same region
1325    pub async fn get_regional_peers(&self) -> Result<Vec<OxirsNodeId>> {
1326        if let Some(region_manager) = &self.region_manager {
1327            let region_id = region_manager.get_local_region();
1328            Ok(region_manager.get_nodes_in_region(region_id).await)
1329        } else {
1330            Err(ClusterError::Config(
1331                "Multi-region not configured".to_string(),
1332            ))
1333        }
1334    }
1335
1336    /// Get optimal leader candidates considering region affinity
1337    pub async fn get_regional_leader_candidates(&self) -> Result<Vec<OxirsNodeId>> {
1338        if let Some(region_manager) = &self.region_manager {
1339            let region_id = region_manager.get_local_region();
1340            Ok(region_manager.get_leader_candidates(region_id).await)
1341        } else {
1342            // Fall back to regular peer list
1343            Ok(self.config.peers.clone())
1344        }
1345    }
1346
1347    /// Calculate cross-region replication targets
1348    pub async fn get_cross_region_replication_targets(&self) -> Result<Vec<String>> {
1349        if let Some(region_manager) = &self.region_manager {
1350            let region_id = region_manager.get_local_region();
1351            region_manager
1352                .calculate_replication_targets(region_id)
1353                .await
1354                .map_err(|e| {
1355                    ClusterError::Other(format!("Failed to calculate replication targets: {e}"))
1356                })
1357        } else {
1358            Ok(Vec::new())
1359        }
1360    }
1361
1362    /// Monitor inter-region latencies and update metrics
1363    pub async fn monitor_region_latencies(&self) -> Result<()> {
1364        if let Some(region_manager) = &self.region_manager {
1365            region_manager.monitor_latencies().await.map_err(|e| {
1366                ClusterError::Other(format!("Failed to monitor region latencies: {e}"))
1367            })
1368        } else {
1369            Ok(())
1370        }
1371    }
1372
1373    /// Get region health status
1374    pub async fn get_region_health(&self, region_id: &str) -> Result<region_manager::RegionHealth> {
1375        if let Some(region_manager) = &self.region_manager {
1376            region_manager
1377                .get_region_health(region_id)
1378                .await
1379                .map_err(|e| ClusterError::Other(format!("Failed to get region health: {e}")))
1380        } else {
1381            Err(ClusterError::Config(
1382                "Multi-region not configured".to_string(),
1383            ))
1384        }
1385    }
1386
1387    /// Perform region failover operation
1388    pub async fn perform_region_failover(
1389        &self,
1390        failed_region: &str,
1391        target_region: &str,
1392    ) -> Result<()> {
1393        if let Some(region_manager) = &self.region_manager {
1394            region_manager
1395                .perform_region_failover(failed_region, target_region)
1396                .await
1397                .map_err(|e| ClusterError::Other(format!("Failed to perform region failover: {e}")))
1398        } else {
1399            Err(ClusterError::Config(
1400                "Multi-region not configured".to_string(),
1401            ))
1402        }
1403    }
1404
1405    /// Get multi-region topology information
1406    pub async fn get_region_topology(&self) -> Result<region_manager::RegionTopology> {
1407        if let Some(region_manager) = &self.region_manager {
1408            Ok(region_manager.get_topology().await)
1409        } else {
1410            Err(ClusterError::Config(
1411                "Multi-region not configured".to_string(),
1412            ))
1413        }
1414    }
1415
1416    /// Add a node to a specific region and availability zone
1417    pub async fn add_node_to_region(
1418        &self,
1419        node_id: OxirsNodeId,
1420        region_id: String,
1421        availability_zone_id: String,
1422        data_center: Option<String>,
1423        rack: Option<String>,
1424    ) -> Result<()> {
1425        if let Some(region_manager) = &self.region_manager {
1426            region_manager
1427                .register_node(node_id, region_id, availability_zone_id, data_center, rack)
1428                .await
1429                .map_err(|e| ClusterError::Other(format!("Failed to add node to region: {e}")))
1430        } else {
1431            Err(ClusterError::Config(
1432                "Multi-region not configured".to_string(),
1433            ))
1434        }
1435    }
1436
1437    /// Get conflict resolver instance
1438    pub fn conflict_resolver(&self) -> &Arc<ConflictResolver> {
1439        &self.conflict_resolver
1440    }
1441
1442    /// Get current vector clock value
1443    pub async fn get_vector_clock(&self) -> VectorClock {
1444        self.local_vector_clock.read().await.clone()
1445    }
1446
1447    /// Update vector clock with received clock
1448    pub async fn update_vector_clock(&self, received_clock: &VectorClock) {
1449        let mut clock = self.local_vector_clock.write().await;
1450        clock.update(received_clock);
1451        clock.increment(self.config.node_id);
1452    }
1453
1454    /// Create a timestamped operation with current vector clock
1455    pub async fn create_timestamped_operation(
1456        &self,
1457        operation: conflict_resolution::RdfOperation,
1458        priority: u32,
1459    ) -> TimestampedOperation {
1460        let mut clock = self.local_vector_clock.write().await;
1461        clock.increment(self.config.node_id);
1462
1463        TimestampedOperation {
1464            operation_id: uuid::Uuid::new_v4().to_string(),
1465            origin_node: self.config.node_id,
1466            vector_clock: clock.clone(),
1467            physical_time: std::time::SystemTime::now(),
1468            operation,
1469            priority,
1470        }
1471    }
1472
1473    /// Detect conflicts in a batch of operations
1474    pub async fn detect_operation_conflicts(
1475        &self,
1476        operations: &[TimestampedOperation],
1477    ) -> Result<Vec<conflict_resolution::ConflictType>> {
1478        self.conflict_resolver
1479            .detect_conflicts(operations)
1480            .await
1481            .map_err(|e| ClusterError::Other(format!("Failed to detect conflicts: {e}")))
1482    }
1483
1484    /// Resolve conflicts using configured strategies
1485    pub async fn resolve_operation_conflicts(
1486        &self,
1487        conflicts: &[conflict_resolution::ConflictType],
1488    ) -> Result<Vec<conflict_resolution::ResolutionResult>> {
1489        self.conflict_resolver
1490            .resolve_conflicts(conflicts)
1491            .await
1492            .map_err(|e| ClusterError::Other(format!("Failed to resolve conflicts: {e}")))
1493    }
1494
1495    /// Submit an operation for conflict-aware processing
1496    pub async fn submit_conflict_aware_operation(
1497        &self,
1498        operation: conflict_resolution::RdfOperation,
1499        priority: u32,
1500    ) -> Result<RdfResponse> {
1501        // Create timestamped operation
1502        let _timestamped_op = self
1503            .create_timestamped_operation(operation.clone(), priority)
1504            .await;
1505
1506        // For now, submit to consensus without conflict detection
1507        // In a full implementation, this would be integrated with the consensus layer
1508        match operation {
1509            conflict_resolution::RdfOperation::Insert {
1510                subject,
1511                predicate,
1512                object,
1513                ..
1514            } => self.insert_triple(&subject, &predicate, &object).await,
1515            conflict_resolution::RdfOperation::Delete {
1516                subject,
1517                predicate,
1518                object,
1519                ..
1520            } => self.delete_triple(&subject, &predicate, &object).await,
1521            conflict_resolution::RdfOperation::Clear { .. } => self.clear_store().await,
1522            conflict_resolution::RdfOperation::Update {
1523                old_triple,
1524                new_triple,
1525                ..
1526            } => {
1527                // Implement as delete + insert
1528                let _delete_result = self
1529                    .delete_triple(&old_triple.0, &old_triple.1, &old_triple.2)
1530                    .await?;
1531                self.insert_triple(&new_triple.0, &new_triple.1, &new_triple.2)
1532                    .await
1533            }
1534            conflict_resolution::RdfOperation::Batch { operations: _ } => {
1535                // Process batch operations sequentially
1536                // Note: This is a simplified implementation that doesn't use recursion
1537                // In a full implementation, each operation would be processed individually
1538                // For now, just return success for batch operations
1539                Ok(RdfResponse::Success)
1540            }
1541        }
1542    }
1543
1544    /// Get conflict resolution statistics
1545    pub async fn get_conflict_resolution_statistics(
1546        &self,
1547    ) -> conflict_resolution::ResolutionStatistics {
1548        self.conflict_resolver.get_statistics().await
1549    }
1550}
1551
1552/// Comprehensive cluster status information
1553#[derive(Debug, Clone)]
1554pub struct ClusterStatus {
1555    /// Local node ID
1556    pub node_id: OxirsNodeId,
1557    /// Local node address
1558    pub address: SocketAddr,
1559    /// Whether this node is the current leader
1560    pub is_leader: bool,
1561    /// Current Raft term
1562    pub current_term: u64,
1563    /// Number of peer nodes
1564    pub peer_count: usize,
1565    /// Number of triples in the store
1566    pub triple_count: usize,
1567    /// Discovery service statistics
1568    pub discovery_stats: discovery::DiscoveryStats,
1569    /// Replication statistics
1570    pub replication_stats: ReplicationStats,
1571    /// Whether the node is currently running
1572    pub is_running: bool,
1573    /// Multi-region status (if enabled)
1574    pub region_status: Option<RegionStatus>,
1575}
1576
1577/// Multi-region status information
1578#[derive(Debug, Clone)]
1579pub struct RegionStatus {
1580    /// Current region ID
1581    pub region_id: String,
1582    /// Current availability zone ID
1583    pub availability_zone_id: String,
1584    /// Number of nodes in the same region
1585    pub regional_peer_count: usize,
1586    /// Total number of regions in topology
1587    pub total_regions: usize,
1588    /// Whether multi-region monitoring is active
1589    pub monitoring_active: bool,
1590}
1591
1592/// Distributed RDF store (simplified interface)
1593pub struct DistributedStore {
1594    node: ClusterNode,
1595}
1596
1597impl DistributedStore {
1598    /// Create a new distributed store
1599    pub async fn new(config: NodeConfig) -> Result<Self> {
1600        let node = ClusterNode::new(config).await?;
1601        Ok(Self { node })
1602    }
1603
1604    /// Start the distributed store
1605    pub async fn start(&mut self) -> Result<()> {
1606        self.node.start().await
1607    }
1608
1609    /// Stop the distributed store
1610    pub async fn stop(&mut self) -> Result<()> {
1611        self.node.stop().await
1612    }
1613
1614    /// Insert a triple (only on leader)
1615    pub async fn insert_triple(
1616        &mut self,
1617        subject: &str,
1618        predicate: &str,
1619        object: &str,
1620    ) -> Result<()> {
1621        let _response = self.node.insert_triple(subject, predicate, object).await?;
1622        Ok(())
1623    }
1624
1625    /// Query triples using SPARQL
1626    pub async fn query_sparql(&self, sparql: &str) -> Result<Vec<String>> {
1627        self.node.query_sparql(sparql).await
1628    }
1629
1630    /// Query triples by pattern
1631    pub async fn query_pattern(
1632        &self,
1633        subject: Option<&str>,
1634        predicate: Option<&str>,
1635        object: Option<&str>,
1636    ) -> Vec<(String, String, String)> {
1637        self.node.query_triples(subject, predicate, object).await
1638    }
1639
1640    /// Get cluster status
1641    pub async fn get_status(&self) -> ClusterStatus {
1642        self.node.get_status().await
1643    }
1644}
1645
1646/// Re-export commonly used types
1647pub use consensus::ConsensusError;
1648pub use discovery::DiscoveryError;
1649pub use replication::ReplicationError;
1650
1651#[cfg(test)]
1652mod tests {
1653    use super::*;
1654    use std::net::{IpAddr, Ipv4Addr};
1655
1656    #[tokio::test]
1657    async fn test_node_config_creation() {
1658        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1659        let config = NodeConfig::new(1, addr);
1660
1661        assert_eq!(config.node_id, 1);
1662        assert_eq!(config.address, addr);
1663        assert_eq!(config.data_dir, "./data/node-1");
1664        assert!(config.peers.is_empty());
1665        assert!(config.discovery.is_some());
1666        assert!(config.replication_strategy.is_some());
1667        assert!(config.region_config.is_none());
1668    }
1669
1670    #[tokio::test]
1671    async fn test_node_config_add_peer() {
1672        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1673        let mut config = NodeConfig::new(1, addr);
1674
1675        config.add_peer(2);
1676        config.add_peer(3);
1677        config.add_peer(2); // Duplicate should be ignored
1678
1679        assert_eq!(config.peers, vec![2, 3]);
1680    }
1681
1682    #[tokio::test]
1683    async fn test_node_config_no_self_peer() {
1684        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1685        let mut config = NodeConfig::new(1, addr);
1686
1687        config.add_peer(1); // Should not add self
1688
1689        assert!(config.peers.is_empty());
1690    }
1691
1692    #[tokio::test]
1693    async fn test_cluster_node_creation() {
1694        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1695        let config = NodeConfig::new(1, addr);
1696
1697        let node = ClusterNode::new(config).await;
1698        assert!(node.is_ok());
1699
1700        let node = node.unwrap();
1701        assert_eq!(node.config.node_id, 1);
1702        assert_eq!(node.config.address, addr);
1703    }
1704
1705    #[tokio::test]
1706    async fn test_cluster_node_empty_data_dir_error() {
1707        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1708        let mut config = NodeConfig::new(1, addr);
1709        config.data_dir = String::new();
1710
1711        let result = ClusterNode::new(config).await;
1712        assert!(result.is_err());
1713        if let Err(e) = result {
1714            assert!(e.to_string().contains("Data directory cannot be empty"));
1715        }
1716    }
1717
1718    #[tokio::test]
1719    async fn test_distributed_store_creation() {
1720        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1721        let config = NodeConfig::new(1, addr);
1722
1723        let store = DistributedStore::new(config).await;
1724        assert!(store.is_ok());
1725    }
1726
1727    #[test]
1728    fn test_cluster_error_types() {
1729        let err = ClusterError::Config("test error".to_string());
1730        assert!(err.to_string().contains("Configuration error: test error"));
1731
1732        let err = ClusterError::NotLeader;
1733        assert_eq!(err.to_string(), "Not the leader node");
1734
1735        let err = ClusterError::Network("connection failed".to_string());
1736        assert!(err.to_string().contains("Network error: connection failed"));
1737    }
1738}