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