nixfleet_reconciler/planner_gates/
mod.rs1pub mod channel_edges;
25pub mod compliance_wave;
26pub mod disruption_budget;
27pub mod host_edges;
28pub mod quarantine;
29pub mod wave_promotion;
30
31#[cfg(test)]
32mod tests;
33
34use crate::planner_types::{
35 ChannelId, ClosureHash, FleetState, HostId, QuarantineSet, RolloutId, SignedManifestSet,
36};
37
38#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum GateBlock {
44 ChannelEdges {
45 predecessor_channel: String,
46 },
47 WavePromotion {
48 host_wave: u32,
49 current_wave: u32,
50 },
51 HostUnstaged {
60 channel: String,
61 },
62 HostEdge {
63 gating_host: String,
64 },
65 DisruptionBudget {
66 in_flight: u32,
67 max: u32,
68 selector_summary: String,
69 },
70 ComplianceWave {
71 failing_events_count: usize,
72 host_wave: u32,
73 },
74 Quarantined {
75 channel: String,
76 closure_hash: String,
77 },
78}
79
80impl GateBlock {
81 pub fn reason(&self) -> String {
83 match self {
84 GateBlock::ChannelEdges {
85 predecessor_channel,
86 } => {
87 format!("channelEdges predecessor channel '{predecessor_channel}' not converged")
88 }
89 GateBlock::WavePromotion {
90 host_wave,
91 current_wave,
92 } => {
93 format!("wave-promotion: host_wave={host_wave} > current_wave={current_wave}")
94 }
95 GateBlock::HostUnstaged { channel } => {
96 format!(
97 "wave-promotion: channel '{channel}' declares waves but this host is not assigned to any of them — fix the fleet declaration or the host's tag/wave selector"
98 )
99 }
100 GateBlock::HostEdge { gating_host } => {
101 format!("host-edge: gating host '{gating_host}' not yet Converged")
102 }
103 GateBlock::DisruptionBudget {
104 in_flight,
105 max,
106 selector_summary,
107 } => format!("disruption-budget: {in_flight}/{max} in flight ({selector_summary})"),
108 GateBlock::ComplianceWave {
109 failing_events_count,
110 host_wave,
111 } => format!(
112 "compliance-wave: {failing_events_count} outstanding failure(s) on hosts in wave < {host_wave}"
113 ),
114 GateBlock::Quarantined {
115 channel,
116 closure_hash,
117 } => format!(
118 "channel {channel} closure {closure_hash} quarantined (sustained probe failures); push a new closure to clear"
119 ),
120 }
121 }
122
123 pub fn discriminator(&self) -> &'static str {
125 match self {
126 GateBlock::ChannelEdges { .. } => "channel-edges",
127 GateBlock::WavePromotion { .. } => "wave-promotion",
128 GateBlock::HostUnstaged { .. } => "wave-promotion-host-unstaged",
129 GateBlock::HostEdge { .. } => "host-edge",
130 GateBlock::DisruptionBudget { .. } => "disruption-budget",
131 GateBlock::ComplianceWave { .. } => "compliance-wave",
132 GateBlock::Quarantined { .. } => "quarantine",
133 }
134 }
135}
136
137#[allow(clippy::too_many_arguments)]
150pub fn evaluate_for_dispatch(
151 fleet_state: &FleetState,
152 manifests: &SignedManifestSet,
153 quarantines: &QuarantineSet,
154 rollout_id: &RolloutId,
155 host: &HostId,
156 target_closure: &ClosureHash,
157 channel: &ChannelId,
158 tick_dispatched: &std::collections::HashMap<disruption_budget::BudgetId, u32>,
159) -> Option<GateBlock> {
160 if let Some(b) = quarantine::check(quarantines, channel, target_closure) {
161 return Some(b);
162 }
163 if let Some(b) = channel_edges::check(fleet_state, manifests, channel) {
164 return Some(b);
165 }
166 if let Some(b) = wave_promotion::check(fleet_state, manifests, host, rollout_id) {
167 return Some(b);
168 }
169 if let Some(b) = host_edges::check(fleet_state, manifests, host, rollout_id) {
170 return Some(b);
171 }
172 if let Some(b) = disruption_budget::check(fleet_state, rollout_id, host, tick_dispatched) {
173 return Some(b);
174 }
175 if let Some(b) = compliance_wave::check(fleet_state, manifests, host, rollout_id) {
176 return Some(b);
177 }
178 None
179}