monitord/
unit_constants.rs

1//! # Unit Constants Module
2//!
3//! Shared constants and enums for systemd unit states and operations.
4//! Reference: <https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html>
5
6use int_enum::IntEnum;
7use serde_repr::*;
8use strum_macros::EnumIter;
9use strum_macros::EnumString;
10
11/// Possible systemd unit active states enumerated
12#[allow(non_camel_case_types)]
13#[derive(
14    Serialize_repr,
15    Deserialize_repr,
16    Clone,
17    Copy,
18    Debug,
19    Default,
20    Eq,
21    PartialEq,
22    EnumIter,
23    EnumString,
24    IntEnum,
25    strum_macros::Display,
26)]
27#[repr(u8)]
28pub enum SystemdUnitActiveState {
29    #[default]
30    unknown = 0,
31    active = 1,
32    reloading = 2,
33    inactive = 3,
34    failed = 4,
35    activating = 5,
36    deactivating = 6,
37}
38
39/// Possible systemd unit load states enumerated
40#[allow(non_camel_case_types)]
41#[derive(
42    Serialize_repr,
43    Deserialize_repr,
44    Clone,
45    Copy,
46    Debug,
47    Default,
48    Eq,
49    PartialEq,
50    EnumIter,
51    EnumString,
52    IntEnum,
53    strum_macros::Display,
54)]
55#[repr(u8)]
56pub enum SystemdUnitLoadState {
57    #[default]
58    unknown = 0,
59    loaded = 1,
60    error = 2,
61    masked = 3,
62    not_found = 4,
63}
64
65/// Check if we're a loaded unit and if so evaluate if we're active or not
66/// If we're not
67/// Only potentially mark unhealthy for LOADED units that are not active
68pub fn is_unit_unhealthy(
69    active_state: SystemdUnitActiveState,
70    load_state: SystemdUnitLoadState,
71) -> bool {
72    match load_state {
73        // We're loaded so let's see if we're active or not
74        SystemdUnitLoadState::loaded => !matches!(active_state, SystemdUnitActiveState::active),
75        // An admin can change a unit to be masked on purpose
76        // so we are going to ignore all masked units due to that
77        SystemdUnitLoadState::masked => false,
78        // Otherwise, we're unhealthy
79        _ => true,
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::str::FromStr;
87    use strum::IntoEnumIterator;
88
89    #[test]
90    fn test_is_unit_unhealthy() {
91        // Obvious active/loaded is healthy
92        assert!(!is_unit_unhealthy(
93            SystemdUnitActiveState::active,
94            SystemdUnitLoadState::loaded
95        ));
96        // Not active + loaded is not healthy
97        assert!(is_unit_unhealthy(
98            SystemdUnitActiveState::activating,
99            SystemdUnitLoadState::loaded
100        ));
101        // Not loaded + anything is just marked healthy as we're not expecting it to ever be healthy
102        assert!(!is_unit_unhealthy(
103            SystemdUnitActiveState::activating,
104            SystemdUnitLoadState::masked
105        ));
106        // Make error + not_found unhealthy too
107        assert!(is_unit_unhealthy(
108            SystemdUnitActiveState::deactivating,
109            SystemdUnitLoadState::not_found
110        ));
111        assert!(is_unit_unhealthy(
112            // Can never really be active here with error, but check we ignore it
113            SystemdUnitActiveState::active,
114            SystemdUnitLoadState::error,
115        ));
116    }
117
118    #[test]
119    fn test_iterators() {
120        assert!(SystemdUnitActiveState::iter().collect::<Vec<_>>().len() > 0);
121        assert!(SystemdUnitLoadState::iter().collect::<Vec<_>>().len() > 0);
122    }
123
124    #[test]
125    fn test_active_state_from_str() {
126        assert_eq!(
127            SystemdUnitActiveState::from_str("active").unwrap(),
128            SystemdUnitActiveState::active
129        );
130        assert_eq!(
131            SystemdUnitActiveState::from_str("reloading").unwrap(),
132            SystemdUnitActiveState::reloading
133        );
134        assert_eq!(
135            SystemdUnitActiveState::from_str("inactive").unwrap(),
136            SystemdUnitActiveState::inactive
137        );
138        assert_eq!(
139            SystemdUnitActiveState::from_str("failed").unwrap(),
140            SystemdUnitActiveState::failed
141        );
142        assert_eq!(
143            SystemdUnitActiveState::from_str("activating").unwrap(),
144            SystemdUnitActiveState::activating
145        );
146        assert_eq!(
147            SystemdUnitActiveState::from_str("deactivating").unwrap(),
148            SystemdUnitActiveState::deactivating
149        );
150        assert_eq!(
151            SystemdUnitActiveState::from_str("unknown").unwrap(),
152            SystemdUnitActiveState::unknown
153        );
154    }
155
156    #[test]
157    fn test_active_state_from_str_invalid() {
158        assert!(SystemdUnitActiveState::from_str("invalid").is_err());
159        assert!(SystemdUnitActiveState::from_str("").is_err());
160        assert!(SystemdUnitActiveState::from_str("ACTIVE").is_err());
161    }
162
163    #[test]
164    fn test_load_state_from_str() {
165        assert_eq!(
166            SystemdUnitLoadState::from_str("loaded").unwrap(),
167            SystemdUnitLoadState::loaded
168        );
169        assert_eq!(
170            SystemdUnitLoadState::from_str("error").unwrap(),
171            SystemdUnitLoadState::error
172        );
173        assert_eq!(
174            SystemdUnitLoadState::from_str("masked").unwrap(),
175            SystemdUnitLoadState::masked
176        );
177        assert_eq!(
178            SystemdUnitLoadState::from_str("not_found").unwrap(),
179            SystemdUnitLoadState::not_found
180        );
181        assert_eq!(
182            SystemdUnitLoadState::from_str("unknown").unwrap(),
183            SystemdUnitLoadState::unknown
184        );
185    }
186
187    #[test]
188    fn test_load_state_from_str_invalid() {
189        assert!(SystemdUnitLoadState::from_str("invalid").is_err());
190        assert!(SystemdUnitLoadState::from_str("").is_err());
191        assert!(SystemdUnitLoadState::from_str("LOADED").is_err());
192    }
193
194    #[test]
195    fn test_active_state_display() {
196        assert_eq!(format!("{}", SystemdUnitActiveState::active), "active");
197        assert_eq!(
198            format!("{}", SystemdUnitActiveState::reloading),
199            "reloading"
200        );
201        assert_eq!(format!("{}", SystemdUnitActiveState::inactive), "inactive");
202        assert_eq!(format!("{}", SystemdUnitActiveState::failed), "failed");
203        assert_eq!(
204            format!("{}", SystemdUnitActiveState::activating),
205            "activating"
206        );
207        assert_eq!(
208            format!("{}", SystemdUnitActiveState::deactivating),
209            "deactivating"
210        );
211        assert_eq!(format!("{}", SystemdUnitActiveState::unknown), "unknown");
212    }
213
214    #[test]
215    fn test_load_state_display() {
216        assert_eq!(format!("{}", SystemdUnitLoadState::loaded), "loaded");
217        assert_eq!(format!("{}", SystemdUnitLoadState::error), "error");
218        assert_eq!(format!("{}", SystemdUnitLoadState::masked), "masked");
219        assert_eq!(format!("{}", SystemdUnitLoadState::not_found), "not_found");
220        assert_eq!(format!("{}", SystemdUnitLoadState::unknown), "unknown");
221    }
222
223    #[test]
224    fn test_active_state_default() {
225        let state: SystemdUnitActiveState = Default::default();
226        assert_eq!(state, SystemdUnitActiveState::unknown);
227    }
228
229    #[test]
230    fn test_load_state_default() {
231        let state: SystemdUnitLoadState = Default::default();
232        assert_eq!(state, SystemdUnitLoadState::unknown);
233    }
234
235    #[test]
236    fn test_active_state_clone() {
237        let state = SystemdUnitActiveState::active;
238        let cloned = state.clone();
239        assert_eq!(state, cloned);
240    }
241
242    #[test]
243    fn test_load_state_clone() {
244        let state = SystemdUnitLoadState::loaded;
245        let cloned = state.clone();
246        assert_eq!(state, cloned);
247    }
248
249    #[test]
250    fn test_active_state_debug() {
251        let state = SystemdUnitActiveState::active;
252        let debug_str = format!("{:?}", state);
253        assert!(debug_str.contains("active"));
254    }
255
256    #[test]
257    fn test_load_state_debug() {
258        let state = SystemdUnitLoadState::loaded;
259        let debug_str = format!("{:?}", state);
260        assert!(debug_str.contains("loaded"));
261    }
262
263    #[test]
264    fn test_active_state_equality() {
265        assert_eq!(
266            SystemdUnitActiveState::active,
267            SystemdUnitActiveState::active
268        );
269        assert_ne!(
270            SystemdUnitActiveState::active,
271            SystemdUnitActiveState::inactive
272        );
273    }
274
275    #[test]
276    fn test_load_state_equality() {
277        assert_eq!(SystemdUnitLoadState::loaded, SystemdUnitLoadState::loaded);
278        assert_ne!(SystemdUnitLoadState::loaded, SystemdUnitLoadState::masked);
279    }
280
281    #[test]
282    fn test_active_state_int_enum() {
283        assert_eq!(SystemdUnitActiveState::unknown as u8, 0);
284        assert_eq!(SystemdUnitActiveState::active as u8, 1);
285        assert_eq!(SystemdUnitActiveState::reloading as u8, 2);
286        assert_eq!(SystemdUnitActiveState::inactive as u8, 3);
287        assert_eq!(SystemdUnitActiveState::failed as u8, 4);
288        assert_eq!(SystemdUnitActiveState::activating as u8, 5);
289        assert_eq!(SystemdUnitActiveState::deactivating as u8, 6);
290    }
291
292    #[test]
293    fn test_load_state_int_enum() {
294        assert_eq!(SystemdUnitLoadState::unknown as u8, 0);
295        assert_eq!(SystemdUnitLoadState::loaded as u8, 1);
296        assert_eq!(SystemdUnitLoadState::error as u8, 2);
297        assert_eq!(SystemdUnitLoadState::masked as u8, 3);
298        assert_eq!(SystemdUnitLoadState::not_found as u8, 4);
299    }
300
301    #[test]
302    fn test_active_state_from_int() {
303        assert_eq!(
304            SystemdUnitActiveState::try_from(0).unwrap(),
305            SystemdUnitActiveState::unknown
306        );
307        assert_eq!(
308            SystemdUnitActiveState::try_from(1).unwrap(),
309            SystemdUnitActiveState::active
310        );
311        assert_eq!(
312            SystemdUnitActiveState::try_from(2).unwrap(),
313            SystemdUnitActiveState::reloading
314        );
315        assert_eq!(
316            SystemdUnitActiveState::try_from(3).unwrap(),
317            SystemdUnitActiveState::inactive
318        );
319        assert_eq!(
320            SystemdUnitActiveState::try_from(4).unwrap(),
321            SystemdUnitActiveState::failed
322        );
323        assert_eq!(
324            SystemdUnitActiveState::try_from(5).unwrap(),
325            SystemdUnitActiveState::activating
326        );
327        assert_eq!(
328            SystemdUnitActiveState::try_from(6).unwrap(),
329            SystemdUnitActiveState::deactivating
330        );
331    }
332
333    #[test]
334    fn test_load_state_from_int() {
335        assert_eq!(
336            SystemdUnitLoadState::try_from(0).unwrap(),
337            SystemdUnitLoadState::unknown
338        );
339        assert_eq!(
340            SystemdUnitLoadState::try_from(1).unwrap(),
341            SystemdUnitLoadState::loaded
342        );
343        assert_eq!(
344            SystemdUnitLoadState::try_from(2).unwrap(),
345            SystemdUnitLoadState::error
346        );
347        assert_eq!(
348            SystemdUnitLoadState::try_from(3).unwrap(),
349            SystemdUnitLoadState::masked
350        );
351        assert_eq!(
352            SystemdUnitLoadState::try_from(4).unwrap(),
353            SystemdUnitLoadState::not_found
354        );
355    }
356
357    #[test]
358    fn test_active_state_from_int_invalid() {
359        assert!(SystemdUnitActiveState::try_from(255).is_err());
360        assert!(SystemdUnitActiveState::try_from(100).is_err());
361    }
362
363    #[test]
364    fn test_load_state_from_int_invalid() {
365        assert!(SystemdUnitLoadState::try_from(255).is_err());
366        assert!(SystemdUnitLoadState::try_from(100).is_err());
367    }
368
369    #[test]
370    fn test_is_unit_unhealthy_all_combinations() {
371        // Test loaded state with all active states
372        assert!(!is_unit_unhealthy(
373            SystemdUnitActiveState::active,
374            SystemdUnitLoadState::loaded
375        ));
376        assert!(is_unit_unhealthy(
377            SystemdUnitActiveState::reloading,
378            SystemdUnitLoadState::loaded
379        ));
380        assert!(is_unit_unhealthy(
381            SystemdUnitActiveState::inactive,
382            SystemdUnitLoadState::loaded
383        ));
384        assert!(is_unit_unhealthy(
385            SystemdUnitActiveState::failed,
386            SystemdUnitLoadState::loaded
387        ));
388        assert!(is_unit_unhealthy(
389            SystemdUnitActiveState::activating,
390            SystemdUnitLoadState::loaded
391        ));
392        assert!(is_unit_unhealthy(
393            SystemdUnitActiveState::deactivating,
394            SystemdUnitLoadState::loaded
395        ));
396        assert!(is_unit_unhealthy(
397            SystemdUnitActiveState::unknown,
398            SystemdUnitLoadState::loaded
399        ));
400
401        // Test masked state with all active states (always healthy)
402        assert!(!is_unit_unhealthy(
403            SystemdUnitActiveState::active,
404            SystemdUnitLoadState::masked
405        ));
406        assert!(!is_unit_unhealthy(
407            SystemdUnitActiveState::reloading,
408            SystemdUnitLoadState::masked
409        ));
410        assert!(!is_unit_unhealthy(
411            SystemdUnitActiveState::inactive,
412            SystemdUnitLoadState::masked
413        ));
414        assert!(!is_unit_unhealthy(
415            SystemdUnitActiveState::failed,
416            SystemdUnitLoadState::masked
417        ));
418        assert!(!is_unit_unhealthy(
419            SystemdUnitActiveState::activating,
420            SystemdUnitLoadState::masked
421        ));
422        assert!(!is_unit_unhealthy(
423            SystemdUnitActiveState::deactivating,
424            SystemdUnitLoadState::masked
425        ));
426        assert!(!is_unit_unhealthy(
427            SystemdUnitActiveState::unknown,
428            SystemdUnitLoadState::masked
429        ));
430
431        // Test error state with all active states (always unhealthy)
432        assert!(is_unit_unhealthy(
433            SystemdUnitActiveState::active,
434            SystemdUnitLoadState::error
435        ));
436        assert!(is_unit_unhealthy(
437            SystemdUnitActiveState::reloading,
438            SystemdUnitLoadState::error
439        ));
440        assert!(is_unit_unhealthy(
441            SystemdUnitActiveState::inactive,
442            SystemdUnitLoadState::error
443        ));
444        assert!(is_unit_unhealthy(
445            SystemdUnitActiveState::failed,
446            SystemdUnitLoadState::error
447        ));
448        assert!(is_unit_unhealthy(
449            SystemdUnitActiveState::activating,
450            SystemdUnitLoadState::error
451        ));
452        assert!(is_unit_unhealthy(
453            SystemdUnitActiveState::deactivating,
454            SystemdUnitLoadState::error
455        ));
456        assert!(is_unit_unhealthy(
457            SystemdUnitActiveState::unknown,
458            SystemdUnitLoadState::error
459        ));
460
461        // Test not_found state with all active states (always unhealthy)
462        assert!(is_unit_unhealthy(
463            SystemdUnitActiveState::active,
464            SystemdUnitLoadState::not_found
465        ));
466        assert!(is_unit_unhealthy(
467            SystemdUnitActiveState::reloading,
468            SystemdUnitLoadState::not_found
469        ));
470        assert!(is_unit_unhealthy(
471            SystemdUnitActiveState::inactive,
472            SystemdUnitLoadState::not_found
473        ));
474        assert!(is_unit_unhealthy(
475            SystemdUnitActiveState::failed,
476            SystemdUnitLoadState::not_found
477        ));
478        assert!(is_unit_unhealthy(
479            SystemdUnitActiveState::activating,
480            SystemdUnitLoadState::not_found
481        ));
482        assert!(is_unit_unhealthy(
483            SystemdUnitActiveState::deactivating,
484            SystemdUnitLoadState::not_found
485        ));
486        assert!(is_unit_unhealthy(
487            SystemdUnitActiveState::unknown,
488            SystemdUnitLoadState::not_found
489        ));
490
491        // Test unknown state with all active states (always unhealthy)
492        assert!(is_unit_unhealthy(
493            SystemdUnitActiveState::active,
494            SystemdUnitLoadState::unknown
495        ));
496        assert!(is_unit_unhealthy(
497            SystemdUnitActiveState::reloading,
498            SystemdUnitLoadState::unknown
499        ));
500        assert!(is_unit_unhealthy(
501            SystemdUnitActiveState::inactive,
502            SystemdUnitLoadState::unknown
503        ));
504        assert!(is_unit_unhealthy(
505            SystemdUnitActiveState::failed,
506            SystemdUnitLoadState::unknown
507        ));
508        assert!(is_unit_unhealthy(
509            SystemdUnitActiveState::activating,
510            SystemdUnitLoadState::unknown
511        ));
512        assert!(is_unit_unhealthy(
513            SystemdUnitActiveState::deactivating,
514            SystemdUnitLoadState::unknown
515        ));
516        assert!(is_unit_unhealthy(
517            SystemdUnitActiveState::unknown,
518            SystemdUnitLoadState::unknown
519        ));
520    }
521
522    #[test]
523    fn test_active_state_serialization() {
524        let state = SystemdUnitActiveState::active;
525        let serialized = serde_json::to_string(&state).unwrap();
526        assert_eq!(serialized, "1");
527
528        let deserialized: SystemdUnitActiveState = serde_json::from_str(&serialized).unwrap();
529        assert_eq!(deserialized, state);
530    }
531
532    #[test]
533    fn test_load_state_serialization() {
534        let state = SystemdUnitLoadState::loaded;
535        let serialized = serde_json::to_string(&state).unwrap();
536        assert_eq!(serialized, "1");
537
538        let deserialized: SystemdUnitLoadState = serde_json::from_str(&serialized).unwrap();
539        assert_eq!(deserialized, state);
540    }
541
542    #[test]
543    fn test_active_state_all_variants_serialization() {
544        for state in SystemdUnitActiveState::iter() {
545            let serialized = serde_json::to_string(&state).unwrap();
546            let deserialized: SystemdUnitActiveState = serde_json::from_str(&serialized).unwrap();
547            assert_eq!(deserialized, state);
548        }
549    }
550
551    #[test]
552    fn test_load_state_all_variants_serialization() {
553        for state in SystemdUnitLoadState::iter() {
554            let serialized = serde_json::to_string(&state).unwrap();
555            let deserialized: SystemdUnitLoadState = serde_json::from_str(&serialized).unwrap();
556            assert_eq!(deserialized, state);
557        }
558    }
559}