1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3
4use serde::Deserialize;
5use serde::Serialize;
6use ursula_shard::RaftGroupId;
7
8pub type NodeId = u64;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum NodeState {
12 Active,
13 Draining,
14 Disabled,
15 Removed,
16}
17
18impl NodeState {
19 pub fn is_migration_eligible(self) -> bool {
20 matches!(self, Self::Active)
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ClusterNode {
26 pub node_id: NodeId,
27 pub client_url: String,
28 pub cluster_url: String,
29 pub state: NodeState,
30 pub registered_at_ms: u64,
31 pub updated_at_ms: u64,
32 #[serde(default)]
33 pub labels: BTreeMap<String, String>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct DataGroupPlacement {
38 pub raft_group_id: RaftGroupId,
39 pub voters: BTreeSet<NodeId>,
40 pub learners: BTreeSet<NodeId>,
41 pub draining: BTreeSet<NodeId>,
42 pub epoch: u64,
43 pub updated_at_ms: u64,
44}
45
46impl DataGroupPlacement {
47 pub fn empty(raft_group_id: RaftGroupId) -> Self {
48 Self {
49 raft_group_id,
50 voters: BTreeSet::new(),
51 learners: BTreeSet::new(),
52 draining: BTreeSet::new(),
53 epoch: 0,
54 updated_at_ms: 0,
55 }
56 }
57
58 pub fn hosts(&self, node_id: NodeId) -> bool {
59 self.voters.contains(&node_id) || self.learners.contains(&node_id)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum LearnerStatus {
65 Pending,
66 Adding,
67 CaughtUp,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
71pub enum MigrationPhase {
72 Validating,
73 PreparingLocalEngines,
74 AddingLearners,
75 ChangingVoters,
76 VerifyingMembership,
77 CommittingPlacement,
78 Finalizing,
79 Succeeded,
80 Failed,
81}
82
83impl MigrationPhase {
84 pub fn is_running(self) -> bool {
85 !matches!(self, Self::Succeeded | Self::Failed)
86 }
87
88 pub fn can_advance_to(self, next: Self) -> bool {
89 if !self.is_running() || !next.is_running() {
90 return false;
91 }
92 self < next
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct GroupMigration {
98 pub migration_id: u64,
99 pub raft_group_id: RaftGroupId,
100 pub from_voters: BTreeSet<NodeId>,
101 pub target_voters: BTreeSet<NodeId>,
102 pub added_nodes: BTreeSet<NodeId>,
103 pub removed_voters: BTreeSet<NodeId>,
104 pub retain_removed: bool,
105 pub phase: MigrationPhase,
106 pub per_node_learner_status: BTreeMap<NodeId, LearnerStatus>,
107 pub last_error: Option<String>,
108 pub retry_count: u32,
109 pub created_at_ms: u64,
110 pub updated_at_ms: u64,
111}
112
113impl GroupMigration {
114 pub fn is_running(&self) -> bool {
115 self.phase.is_running()
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct MetaConfig {
121 pub initial_meta_voters: BTreeSet<NodeId>,
122 pub default_replication_factor: u32,
123 pub autopilot_enabled: bool,
124}
125
126impl Default for MetaConfig {
127 fn default() -> Self {
128 Self {
129 initial_meta_voters: BTreeSet::new(),
130 default_replication_factor: 3,
131 autopilot_enabled: false,
132 }
133 }
134}