bindy/reconcilers/bind9cluster/
drift.rs1#[allow(clippy::wildcard_imports)]
10use super::types::*;
11use crate::reconcilers::pagination::list_all_paginated;
12
13pub(super) async fn detect_instance_drift(
35 client: &Client,
36 cluster: &Bind9Cluster,
37 namespace: &str,
38 name: &str,
39) -> Result<bool> {
40 let desired_primary = cluster
42 .spec
43 .common
44 .primary
45 .as_ref()
46 .and_then(|p| p.replicas)
47 .unwrap_or(0);
48
49 let desired_secondary = cluster
50 .spec
51 .common
52 .secondary
53 .as_ref()
54 .and_then(|s| s.replicas)
55 .unwrap_or(0);
56
57 let api: Api<Bind9Instance> = Api::namespaced(client.clone(), namespace);
59 let instances = list_all_paginated(&api, ListParams::default()).await?;
60
61 let managed_instances: Vec<_> = instances
63 .into_iter()
64 .filter(|instance| {
65 instance.metadata.labels.as_ref().is_some_and(|labels| {
66 labels.get(BINDY_MANAGED_BY_LABEL) == Some(&MANAGED_BY_BIND9_CLUSTER.to_string())
67 && labels.get(BINDY_CLUSTER_LABEL) == Some(&name.to_string())
68 })
69 })
70 .collect();
71
72 let actual_primary = managed_instances
74 .iter()
75 .filter(|i| i.spec.role == ServerRole::Primary)
76 .count();
77
78 let actual_secondary = managed_instances
79 .iter()
80 .filter(|i| i.spec.role == ServerRole::Secondary)
81 .count();
82
83 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
85 let drift = actual_primary != desired_primary as usize
86 || actual_secondary != desired_secondary as usize;
87
88 if drift {
89 info!(
90 "Instance drift detected for cluster {}/{}: desired (primary={}, secondary={}), actual (primary={}, secondary={})",
91 namespace, name, desired_primary, desired_secondary, actual_primary, actual_secondary
92 );
93 }
94
95 Ok(drift)
96}
97
98#[cfg(test)]
99#[path = "drift_tests.rs"]
100mod drift_tests;