1use schemars::JsonSchema;
2use serde::{Serialize, Deserialize};
3use std::fmt::Debug;
4
5pub trait ConditionType: Clone + Copy + Default + Debug + PartialEq
8where Self: 'static {
9 fn happy() -> Self;
11
12 fn dependents() -> &'static [Self];
14
15 fn is_terminal(&self) -> bool {
17 Self::dependents().contains(self) || *self == Self::happy()
18 }
19
20 fn severity(&self) -> ConditionSeverity {
23 if self.is_terminal() {
24 ConditionSeverity::Error
25 } else {
26 ConditionSeverity::Info
27 }
28 }
29}
30
31pub trait ConditionAccessor<C: ConditionType> {
34 fn conditions(&mut self) -> &mut Conditions<C>;
36
37 fn manager(&mut self) -> ConditionManager<C> {
39 ConditionManager::new(self.conditions())
40 }
41
42 fn is_ready(&mut self) -> bool {
44 self.manager().is_happy()
45 }
46
47 fn mark_false(&mut self, reason: &str, message: Option<String>) {
49 let t = self.manager().get_top_level_condition().type_;
50 self.manager().mark_false(t, reason, message);
51 }
52
53 fn mark_unknown(&mut self) {
56 let t = self.manager().get_top_level_condition().type_;
57 self.manager().mark_unknown(
58 t,
59 "NewObservedGenFailure",
60 Some("unsuccessfully observed a new generation".into())
61 );
62 }
63
64 fn mark_unknown_with_message(&mut self, reason: &str, message: Option<String>) {
65 let t = self.manager().get_top_level_condition().type_;
66 self.manager().mark_unknown(t, reason, message);
67 }
68}
69
70#[derive(Deserialize, Serialize, Clone, Copy, Debug, JsonSchema, PartialEq)]
72pub enum ConditionStatus {
73 True,
74 False,
75 Unknown,
76}
77
78impl Default for ConditionStatus {
79 fn default() -> Self {
80 ConditionStatus::Unknown
81 }
82}
83
84#[derive(Deserialize, Serialize, Clone, Copy, Debug, JsonSchema, PartialEq)]
85#[non_exhaustive]
86pub enum ConditionSeverity {
88 Error,
89 Warning,
90 Info,
91}
92
93impl Default for ConditionSeverity {
94 fn default() -> Self {
95 ConditionSeverity::Error
96 }
97}
98
99impl ConditionSeverity {
100 pub fn is_err(&self) -> bool {
101 *self == ConditionSeverity::Error
102 }
103}
104
105#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema, PartialEq)]
107pub struct Condition<C: ConditionType> {
108 #[serde(rename = "type")]
109 pub type_: C,
110 pub status: ConditionStatus,
111 #[serde(default)]
117 #[serde(skip_serializing_if = "ConditionSeverity::is_err")]
118 pub severity: ConditionSeverity,
119 pub last_transition_time: Option<chrono::DateTime<chrono::Utc>>,
122 pub reason: Option<String>,
123 pub message: Option<String>,
124}
125
126impl<C: ConditionType> Default for Condition<C> {
127 fn default() -> Condition<C> {
128 Condition {
129 type_: C::default(),
130 status: ConditionStatus::default(),
131 severity: ConditionSeverity::default(),
132 last_transition_time: Some(chrono::Utc::now()),
133 reason: None,
134 message: None
135 }
136 }
137}
138
139impl<C: ConditionType> PartialOrd for Condition<C> {
140 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
141 use ConditionStatus::*;
142 use std::cmp::Ordering;
143
144 let time_ord = match (self.last_transition_time, other.last_transition_time) {
145 (Some(left), Some(right)) => left.partial_cmp(&right),
146 _ => None
147 };
148
149 match (self.status, other.status) {
150 (False, False) | (Unknown, Unknown) | (True, True) => match time_ord {
151 Some(ord) => Some(ord),
152 None => Some(Ordering::Equal)
153 },
154 (False, _) | (Unknown, True) => Some(Ordering::Greater),
155 (Unknown, False) | (True, _) => Some(Ordering::Less),
156 }
157 }
158}
159
160impl<C: ConditionType> Condition<C> {
161 fn new(type_: C) -> Self {
162 Condition {
163 type_,
164 ..Default::default()
165 }
166 }
167
168 fn with_status(type_: C, status: ConditionStatus) -> Condition<C> {
169 Condition {
170 status,
171 ..Condition::new(type_)
172 }
173 }
174
175 fn is_true(&self) -> bool {
176 self.status == ConditionStatus::True
177 }
178
179 fn is_false(&self) -> bool {
180 self.status == ConditionStatus::False
181 }
182
183 #[allow(dead_code)]
184 fn is_unknown(&self) -> bool {
185 self.status == ConditionStatus::Unknown
186 }
187}
188
189#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
191pub struct Conditions<C: ConditionType>(Vec<Condition<C>>);
192
193impl<C: ConditionType> Default for Conditions<C> {
194 fn default() -> Self {
195 let iter = [C::happy()]
196 .into_iter()
197 .chain(C::dependents().iter().cloned())
198 .map(Condition::new);
199 Conditions(Vec::from_iter(iter))
200 }
201}
202
203impl<C: ConditionType> Conditions<C> {
204 pub fn with_conditions(conditions: Vec<Condition<C>>) -> Conditions<C> {
205 assert!(
206 conditions.iter().any(|c| c.type_ == C::happy()),
207 "Conditions must be initialized with the happy ConditionType"
208 );
209 assert!(
210 conditions.iter().fold(std::collections::HashSet::new(), |mut acc, c| {
211 acc.insert(format!("{:?}", c.type_));
213 acc
214 }).len() == conditions.len(),
215 "ConditionType must be unique to each Condition"
216 );
217 Conditions(conditions)
218 }
219
220 fn get_cond(&self, type_: &C) -> Option<&Condition<C>> {
221 self.0.iter().find(|c| c.type_ == *type_)
222 }
223
224 fn get_cond_mut(&mut self, type_: &C) -> Option<&mut Condition<C>> {
225 self.0.iter_mut().find(|c| c.type_ == *type_)
226 }
227
228 fn set_cond(&mut self, mut condition: Condition<C>) {
229 match self.get_cond_mut(&condition.type_) {
236 Some(cond) => {
237 let test_cond = Condition {
239 last_transition_time: condition.last_transition_time,
240 ..cond.clone()
242 };
243 if test_cond == condition {
244 return
245 } else {
246 *cond = Condition {
247 last_transition_time: Some(chrono::Utc::now()),
248 ..condition
249 }
250 }
251 }
252 None => {
253 condition.last_transition_time = Some(chrono::Utc::now());
254 self.0.push(condition);
255 }
257 }
258 }
259
260 fn mark_true(&mut self, condition_type: C) {
261 self.set_cond(Condition::with_status(condition_type, ConditionStatus::True))
262 }
263
264 fn mark_true_with_reason(&mut self, condition_type: C, reason: String, message: Option<String>) {
265 self.set_cond(Condition {
266 reason: Some(reason),
267 message,
268 ..Condition::with_status(condition_type, ConditionStatus::True)
269 })
270 }
271
272 fn mark_false(&mut self, condition_type: C, reason: String, message: Option<String>) {
273 self.set_cond(Condition {
274 reason: Some(reason),
275 message,
276 ..Condition::with_status(condition_type, ConditionStatus::False)
277 });
278 }
279
280 fn mark_unknown(&mut self, condition_type: C, reason: String, message: Option<String>) {
281 self.set_cond(Condition {
282 reason: Some(reason),
283 message,
284 ..Condition::with_status(condition_type, ConditionStatus::Unknown)
285 });
286 }
287}
288
289pub struct ConditionManager<'a, C: ConditionType> {
292 conditions: &'a mut Conditions<C>,
293}
294
295impl<'a, C: ConditionType> ConditionManager<'a, C> {
296 pub fn new(conditions: &'a mut Conditions<C>) -> Self {
297 assert!(
298 !C::dependents().contains(&C::happy()),
299 "dependents may not contain happy condition"
300 );
301 ConditionManager { conditions }
302 }
303
304 pub fn get_condition(&self, condition_type: C) -> Option<&Condition<C>> {
305 self.conditions.get_cond(&condition_type)
306 }
307
308 pub fn get_top_level_condition(&self) -> &Condition<C> {
314 self.get_condition(C::happy())
315 .as_ref()
316 .expect("top level condition is initialized")
317 }
318
319 pub fn is_happy(&self) -> bool {
320 self.get_top_level_condition().is_true()
321 }
322
323 fn find_unhappy_dependent(&self) -> Option<&Condition<C>> {
324 self.conditions.0
325 .iter()
326 .filter(|cond| cond.type_ != C::happy() && cond.type_.is_terminal() && !cond.is_true())
328 .reduce(|unhappy, cond| if cond > unhappy { cond } else { unhappy })
330 }
331
332 fn recompute_happiness(&mut self, condition_type: &C) {
334 match self.find_unhappy_dependent() {
335 Some(dependent) => {
336 let cond = Condition {
337 type_: C::happy(),
338 status: dependent.status,
339 reason: dependent.reason.clone(),
340 message: dependent.message.clone(),
341 severity: C::happy().severity(),
342 ..Default::default()
343 };
344 self.conditions.set_cond(cond);
345 },
346 None => if *condition_type != C::happy() {
347 self.conditions.set_cond(Condition {
349 type_: C::happy(),
350 status: ConditionStatus::True,
351 severity: C::happy().severity(),
352 ..Default::default()
353 })
354 }
355 }
356 }
357
358 pub fn mark_true(&mut self, condition_type: C) {
359 self.conditions.mark_true(condition_type);
360 self.recompute_happiness(&condition_type);
361 }
362
363 pub fn mark_true_with_reason(&mut self, condition_type: C, reason: &str, message: Option<String>) {
364 self.conditions.mark_true_with_reason(condition_type, reason.to_string(), message);
365 self.recompute_happiness(&condition_type);
366 }
367
368 pub fn mark_false(&mut self, condition_type: C, reason: &str, message: Option<String>) {
371 self.conditions.mark_false(condition_type, reason.to_string(), message.clone());
372
373 if C::dependents().contains(&condition_type) {
374 self.conditions.mark_false(C::happy(), reason.to_string(), message)
375 }
376 }
377
378 pub fn mark_unknown(&mut self, condition_type: C, reason: &str, message: Option<String>) {
381 self.conditions.mark_unknown(condition_type, reason.to_string(), message.clone());
382
383 if let Some(dependent) = self.find_unhappy_dependent() {
386 if dependent.is_false() {
387 if !self.get_top_level_condition().is_false() {
388 self.mark_false(C::happy(), reason, message);
389 }
390 }
391 } else if condition_type.is_terminal() {
392 self.conditions.mark_unknown(C::happy(), reason.to_string(), message);
393 }
394 }
395}
396
397#[cfg(test)]
398mod test {
399 use super::*;
400 use chrono::TimeZone;
401
402 #[derive(Deserialize, Copy, Clone, Debug, PartialEq)]
403 enum TestCondition {
404 Ready,
405 SinkProvided,
406 OtherCondition,
407 Unimportant
408 }
409
410 impl ConditionType for TestCondition {
411 fn happy() -> Self {
412 TestCondition::Ready
413 }
414
415 fn dependents() -> &'static [Self] {
416 &[TestCondition::SinkProvided, TestCondition::OtherCondition]
417 }
418 }
419
420 impl Default for TestCondition {
421 fn default() -> Self {
422 TestCondition::Ready
423 }
424 }
425
426 #[test]
427 fn find_unhappy_dependent_does_not_sort_vec() {
428 let dt = chrono::Utc.ymd(2022, 1, 1);
429 let mut conditions = Conditions::with_conditions(vec![
430 Condition {
431 type_: TestCondition::Ready,
432 status: ConditionStatus::False,
433 last_transition_time: Some(dt.and_hms(0, 0, 0)),
434 ..Default::default()
435 },
436 Condition {
437 type_: TestCondition::SinkProvided,
438 status: ConditionStatus::False,
439 last_transition_time: Some(dt.and_hms(3, 0, 0)),
440 ..Default::default()
441 },
442 Condition {
443 type_: TestCondition::OtherCondition,
444 status: ConditionStatus::False,
445 last_transition_time: Some(dt.and_hms(2, 0, 0)),
446 ..Default::default()
447 },
448 Condition {
449 type_: TestCondition::Unimportant,
450 status: ConditionStatus::False,
451 last_transition_time: Some(dt.and_hms(2, 0, 0)),
452 ..Default::default()
453 },
454 ]);
455
456 let manager = ConditionManager::new(&mut conditions);
457 let unhappy = manager.find_unhappy_dependent().unwrap();
458 assert_eq!(unhappy.type_, TestCondition::SinkProvided);
460 assert_eq!(unhappy.status, ConditionStatus::False);
461 assert_eq!(unhappy.last_transition_time.unwrap(), dt.and_hms(3, 0, 0));
462 let mut iter = conditions.0.iter();
464 assert_eq!(iter.next().unwrap().type_, TestCondition::Ready);
465 assert_eq!(iter.next().unwrap().type_, TestCondition::SinkProvided);
466 assert_eq!(iter.next().unwrap().type_, TestCondition::OtherCondition);
467 assert_eq!(iter.next().unwrap().type_, TestCondition::Unimportant);
468 }
469
470 #[test]
471 fn condition_type_deserializes() {
472 let condition_type: TestCondition = serde_json::from_value(serde_json::json!(
473 "SinkProvided"
474 )).unwrap();
475 assert_eq!(condition_type, TestCondition::SinkProvided);
476 let condition_type: TestCondition = serde_json::from_value(serde_json::json!(
477 "Ready"
478 )).unwrap();
479 assert_eq!(condition_type, TestCondition::Ready);
480 let condition_type: Result<TestCondition, _> = serde_json::from_value(serde_json::json!(
481 "Succeeded"
482 ));
483 assert!(condition_type.is_err());
484 }
485}