Skip to content

Commit 3406bfd

Browse files
authored
Merge branch 'main' into chore/add-crd-versioning
2 parents b0ed7d8 + 1815bc1 commit 3406bfd

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

crates/stackable-operator/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77
### Added
88

99
- Add Deployments to `ClusterResource`s ([#992]).
10+
- Add `DeploymentConditionBuilder` ([#993]).
1011

1112
### Changed
1213

@@ -22,6 +23,7 @@ All notable changes to this project will be documented in this file.
2223
[#968]: https://github.com/stackabletech/operator-rs/pull/968
2324
[#989]: https://github.com/stackabletech/operator-rs/pull/989
2425
[#992]: https://github.com/stackabletech/operator-rs/pull/992
26+
[#993]: https://github.com/stackabletech/operator-rs/pull/993
2527

2628
## [0.87.5] - 2025-03-19
2729

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use std::cmp;
2+
3+
use k8s_openapi::api::apps::v1::Deployment;
4+
use kube::ResourceExt;
5+
6+
use crate::status::condition::{
7+
ClusterCondition, ClusterConditionSet, ClusterConditionStatus, ClusterConditionType,
8+
ConditionBuilder,
9+
};
10+
11+
/// Default implementation to build [`ClusterCondition`]s for
12+
/// `Deployment` resources.
13+
///
14+
/// Currently only the `ClusterConditionType::Available` is implemented. This will be extended
15+
/// to support all `ClusterConditionType`s in the future.
16+
#[derive(Default)]
17+
pub struct DeploymentConditionBuilder {
18+
deployments: Vec<Deployment>,
19+
}
20+
21+
impl ConditionBuilder for DeploymentConditionBuilder {
22+
fn build_conditions(&self) -> ClusterConditionSet {
23+
vec![self.available()].into()
24+
}
25+
}
26+
27+
impl DeploymentConditionBuilder {
28+
pub fn add(&mut self, deployment: Deployment) {
29+
self.deployments.push(deployment);
30+
}
31+
32+
fn available(&self) -> ClusterCondition {
33+
let mut available = ClusterConditionStatus::True;
34+
let mut unavailable_resources = vec![];
35+
for deployment in &self.deployments {
36+
let current_status = Self::deployment_available(deployment);
37+
38+
if current_status != ClusterConditionStatus::True {
39+
unavailable_resources.push(deployment.name_any())
40+
}
41+
42+
available = cmp::max(available, current_status);
43+
}
44+
45+
// We need to sort here to make sure roles and role groups are not changing position
46+
// due to the HashMap (random order) logic.
47+
unavailable_resources.sort();
48+
49+
let message = match available {
50+
ClusterConditionStatus::True => {
51+
"All Deployments have the requested amount of ready replicas.".to_string()
52+
}
53+
ClusterConditionStatus::False => {
54+
format!("Deployment {unavailable_resources:?} missing ready replicas.")
55+
}
56+
ClusterConditionStatus::Unknown => {
57+
"Deployment status cannot be determined.".to_string()
58+
}
59+
};
60+
61+
ClusterCondition {
62+
reason: None,
63+
message: Some(message),
64+
status: available,
65+
type_: ClusterConditionType::Available,
66+
last_transition_time: None,
67+
last_update_time: None,
68+
}
69+
}
70+
71+
/// Returns a condition "Available: True" if the number of requested replicas matches
72+
/// the number of available replicas. In addition, there needs to be at least one replica
73+
/// available.
74+
fn deployment_available(deployment: &Deployment) -> ClusterConditionStatus {
75+
let requested_replicas = deployment
76+
.spec
77+
.as_ref()
78+
.and_then(|spec| spec.replicas)
79+
.unwrap_or_default();
80+
let available_replicas = deployment
81+
.status
82+
.as_ref()
83+
.and_then(|status| status.available_replicas)
84+
.unwrap_or_default();
85+
86+
if requested_replicas == available_replicas && requested_replicas != 0 {
87+
ClusterConditionStatus::True
88+
} else {
89+
ClusterConditionStatus::False
90+
}
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec, DeploymentStatus};
97+
98+
use crate::status::condition::{
99+
deployment::DeploymentConditionBuilder, ClusterCondition, ClusterConditionStatus,
100+
ClusterConditionType, ConditionBuilder,
101+
};
102+
103+
fn build_deployment(spec_replicas: i32, available_replicas: i32) -> Deployment {
104+
Deployment {
105+
spec: Some(DeploymentSpec {
106+
replicas: Some(spec_replicas),
107+
..DeploymentSpec::default()
108+
}),
109+
status: Some(DeploymentStatus {
110+
available_replicas: Some(available_replicas),
111+
..DeploymentStatus::default()
112+
}),
113+
..Deployment::default()
114+
}
115+
}
116+
117+
#[test]
118+
fn available() {
119+
let deployment = build_deployment(3, 3);
120+
121+
assert_eq!(
122+
DeploymentConditionBuilder::deployment_available(&deployment),
123+
ClusterConditionStatus::True
124+
);
125+
}
126+
127+
#[test]
128+
fn unavailable() {
129+
let deployment = build_deployment(3, 2);
130+
131+
assert_eq!(
132+
DeploymentConditionBuilder::deployment_available(&deployment),
133+
ClusterConditionStatus::False
134+
);
135+
136+
let deployment = build_deployment(3, 4);
137+
138+
assert_eq!(
139+
DeploymentConditionBuilder::deployment_available(&deployment),
140+
ClusterConditionStatus::False
141+
);
142+
}
143+
144+
#[test]
145+
fn condition_available() {
146+
let mut deployment_condition_builder = DeploymentConditionBuilder::default();
147+
deployment_condition_builder.add(build_deployment(3, 3));
148+
149+
let conditions = deployment_condition_builder.build_conditions();
150+
151+
let got = conditions
152+
.conditions
153+
.get::<usize>(ClusterConditionType::Available.into())
154+
.cloned()
155+
.unwrap()
156+
.unwrap();
157+
158+
let expected = ClusterCondition {
159+
type_: ClusterConditionType::Available,
160+
status: ClusterConditionStatus::True,
161+
..ClusterCondition::default()
162+
};
163+
164+
assert_eq!(got.type_, expected.type_);
165+
assert_eq!(got.status, expected.status);
166+
}
167+
168+
#[test]
169+
fn condition_unavailable() {
170+
let mut deployment_condition_builder = DeploymentConditionBuilder::default();
171+
deployment_condition_builder.add(build_deployment(3, 2));
172+
173+
let conditions = deployment_condition_builder.build_conditions();
174+
175+
let got = conditions
176+
.conditions
177+
.get::<usize>(ClusterConditionType::Available.into())
178+
.cloned()
179+
.unwrap()
180+
.unwrap();
181+
182+
let expected = ClusterCondition {
183+
type_: ClusterConditionType::Available,
184+
status: ClusterConditionStatus::False,
185+
..ClusterCondition::default()
186+
};
187+
188+
assert_eq!(got.type_, expected.type_);
189+
assert_eq!(got.status, expected.status);
190+
}
191+
}

crates/stackable-operator/src/status/condition/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod daemonset;
2+
pub mod deployment;
23
pub mod operations;
34
pub mod statefulset;
45

0 commit comments

Comments
 (0)