bindy/reconcilers/bind9cluster/
status_helpers.rs1#[allow(clippy::wildcard_imports)]
10use super::types::*;
11
12#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
32pub fn calculate_cluster_status(
33 instances: &[Bind9Instance],
34 namespace: &str,
35 name: &str,
36) -> (i32, i32, Vec<String>, Vec<Condition>) {
37 let instance_count = instances.len() as i32;
39 let instance_names: Vec<String> = instances.iter().map(ResourceExt::name_any).collect();
40
41 let ready_instances = instances
42 .iter()
43 .filter(|instance| {
44 instance
45 .status
46 .as_ref()
47 .and_then(|status| status.conditions.first())
48 .is_some_and(|condition| condition.r#type == "Ready" && condition.status == "True")
49 })
50 .count() as i32;
51
52 info!(
53 "Bind9Cluster {}/{} has {} instances, {} ready",
54 namespace, name, instance_count, ready_instances
55 );
56
57 let mut instance_conditions = Vec::new();
59 for (index, instance) in instances.iter().enumerate() {
60 let instance_name = instance.name_any();
61 let is_instance_ready = instance
62 .status
63 .as_ref()
64 .and_then(|status| status.conditions.first())
65 .is_some_and(|condition| condition.r#type == "Ready" && condition.status == "True");
66
67 let (status, reason, message) = if is_instance_ready {
68 (
69 "True",
70 REASON_READY,
71 format!("Instance {instance_name} is ready"),
72 )
73 } else {
74 (
75 "False",
76 REASON_NOT_READY,
77 format!("Instance {instance_name} is not ready"),
78 )
79 };
80
81 instance_conditions.push(Condition {
82 r#type: bind9_instance_condition_type(index),
83 status: status.to_string(),
84 reason: Some(reason.to_string()),
85 message: Some(message),
86 last_transition_time: Some(Utc::now().to_rfc3339()),
87 });
88 }
89
90 let (encompassing_status, encompassing_reason, encompassing_message) = if instance_count == 0 {
92 debug!("No instances found for cluster");
93 (
94 "False",
95 REASON_NO_CHILDREN,
96 "No instances found for this cluster".to_string(),
97 )
98 } else if ready_instances == instance_count {
99 debug!("All instances ready");
100 (
101 "True",
102 REASON_ALL_READY,
103 format!("All {instance_count} instances are ready"),
104 )
105 } else if ready_instances > 0 {
106 debug!(ready_instances, instance_count, "Cluster progressing");
107 (
108 "False",
109 REASON_PARTIALLY_READY,
110 format!("{ready_instances}/{instance_count} instances are ready"),
111 )
112 } else {
113 debug!("Waiting for instances to become ready");
114 (
115 "False",
116 REASON_NOT_READY,
117 "No instances are ready".to_string(),
118 )
119 };
120
121 let encompassing_condition = Condition {
122 r#type: CONDITION_TYPE_READY.to_string(),
123 status: encompassing_status.to_string(),
124 reason: Some(encompassing_reason.to_string()),
125 message: Some(encompassing_message.clone()),
126 last_transition_time: Some(Utc::now().to_rfc3339()),
127 };
128
129 let mut all_conditions = vec![encompassing_condition];
131 all_conditions.extend(instance_conditions);
132
133 debug!(
134 status = %encompassing_status,
135 message = %encompassing_message,
136 num_conditions = all_conditions.len(),
137 "Determined cluster status"
138 );
139
140 (
141 instance_count,
142 ready_instances,
143 instance_names,
144 all_conditions,
145 )
146}
147
148pub(super) async fn update_status(
166 client: &Client,
167 cluster: &Bind9Cluster,
168 conditions: Vec<Condition>,
169 instance_count: i32,
170 ready_instances: i32,
171 instances: Vec<String>,
172) -> Result<()> {
173 let api: Api<Bind9Cluster> =
174 Api::namespaced(client.clone(), &cluster.namespace().unwrap_or_default());
175
176 let current_status = &cluster.status;
178 let status_changed =
179 if let Some(current) = current_status {
180 if current.instance_count != Some(instance_count)
182 || current.ready_instances != Some(ready_instances)
183 || current.instances != instances
184 {
185 true
186 } else {
187 if current.conditions.len() == conditions.len() {
189 current.conditions.iter().zip(conditions.iter()).any(
191 |(current_cond, new_cond)| {
192 current_cond.r#type != new_cond.r#type
193 || current_cond.status != new_cond.status
194 || current_cond.message != new_cond.message
195 || current_cond.reason != new_cond.reason
196 },
197 )
198 } else {
199 true
200 }
201 }
202 } else {
203 true
205 };
206
207 if !status_changed {
209 debug!(
210 namespace = %cluster.namespace().unwrap_or_default(),
211 name = %cluster.name_any(),
212 "Status unchanged, skipping update"
213 );
214 info!(
215 "Bind9Cluster {}/{} status unchanged, skipping update",
216 cluster.namespace().unwrap_or_default(),
217 cluster.name_any()
218 );
219 return Ok(());
220 }
221
222 debug!(
223 instance_count,
224 ready_instances,
225 instances_count = instances.len(),
226 num_conditions = conditions.len(),
227 "Preparing status update"
228 );
229
230 let new_status = Bind9ClusterStatus {
231 conditions,
232 observed_generation: cluster.metadata.generation,
233 instance_count: Some(instance_count),
234 ready_instances: Some(ready_instances),
235 instances,
236 };
237
238 info!(
239 "Updating Bind9Cluster {}/{} status: {} instances, {} ready",
240 cluster.namespace().unwrap_or_default(),
241 cluster.name_any(),
242 instance_count,
243 ready_instances
244 );
245
246 let patch = json!({ "status": new_status });
247 api.patch_status(
248 &cluster.name_any(),
249 &PatchParams::apply("bindy-controller"),
250 &Patch::Merge(&patch),
251 )
252 .await?;
253
254 Ok(())
255}
256
257#[cfg(test)]
258#[path = "status_helpers_tests.rs"]
259mod status_helpers_tests;