Skip to main content

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