Skip to main content

harmos_domain/model/services/
policy.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Resident Service Stop Deadlines And Restart Credit
11
12/* === Modules === */
13
14/* === Re-Exports === */
15
16/* === Imports === */
17
18// Standard Library Imports
19use std::time::Duration;
20// Third Party Imports
21use serde::{Deserialize, Serialize};
22// Local Imports
23use crate::model::operations::policy::{Backoff, PolicyError};
24
25/* === Constants === */
26
27/* === Macros === */
28
29/* === Type Aliases === */
30
31/* === Structs === */
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct ServicePolicy {
36    pub stop_timeout: Duration,
37    pub restart: RestartPolicy,
38}
39
40/* === Enums === */
41
42// Failed Residents Spend Restart Credit; Sustained Health Replenishes It.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case", deny_unknown_fields)]
45pub enum RestartPolicy {
46    Never,
47    OnFailure { credit: usize, sustained_health: Duration, backoff: Backoff },
48}
49
50/* === Traits === */
51
52/* === Implementations === */
53
54impl ServicePolicy {
55    pub fn validate(self) -> Result<(), PolicyError> {
56        if self.stop_timeout.is_zero() {
57            return Err(PolicyError::Deadline {
58                timeout: self.stop_timeout,
59                attempt_timeout: self.stop_timeout,
60            });
61        }
62        self.restart.validate()
63    }
64}
65
66impl RestartPolicy {
67    pub fn validate(self) -> Result<(), PolicyError> {
68        match self {
69            Self::Never => Ok(()),
70            Self::OnFailure { credit, sustained_health, .. } if credit == 0 || sustained_health.is_zero() => {
71                Err(PolicyError::EmptyRestarts)
72            }
73            Self::OnFailure { backoff, .. } => backoff.validate(),
74        }
75    }
76
77    pub const fn credit(self) -> usize {
78        match self {
79            Self::Never => 0,
80            Self::OnFailure { credit, .. } => credit,
81        }
82    }
83}
84
85/* === Public Functions === */
86
87/* === Private Functions === */
88
89/* === Tests === */
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::Backoff;
95    use crate::Jitter;
96
97    #[test]
98    fn service_credit_and_backoff() {
99        let delay = Duration::from_millis(10);
100        let backoff = Backoff::Exponential {
101            initial: delay,
102            maximum: delay * 4,
103            jitter: Jitter::None,
104        };
105        let restart = RestartPolicy::OnFailure {
106            credit: 2,
107            sustained_health: Duration::from_secs(1),
108            backoff,
109        };
110        assert_eq!(restart.credit(), 2);
111        assert!(ServicePolicy { stop_timeout: delay, restart }.validate().is_ok());
112        assert!(ServicePolicy { stop_timeout: Duration::ZERO, restart }.validate().is_err());
113        assert!(
114            RestartPolicy::OnFailure {
115                credit: 0,
116                sustained_health: delay,
117                backoff
118            }
119            .validate()
120            .is_err()
121        );
122        assert_eq!(backoff.delay(0, 0).unwrap(), delay);
123        assert_eq!(backoff.delay(usize::MAX, 0).unwrap(), delay * 4);
124    }
125}