Skip to main content

ursula_control/
model.rs

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}