1use isoloom_spec::model::{CommandLine, Healthcheck, Machine, seconds};
6
7pub const DEFAULT_INTERVAL: u32 = 5;
9pub const DEFAULT_TIMEOUT: u32 = 3;
11pub const DEFAULT_RETRIES: u32 = 60;
13pub const DEFAULT_START_PERIOD: u32 = 10;
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct Process {
19 pub command: Option<CommandLine>,
21 pub working_dir: Option<String>,
23 pub health: Option<Health>,
25}
26
27impl Process {
28 pub fn of(m: &Machine) -> Process {
30 Process {
31 command: m.command.clone(),
32 working_dir: m.working_dir.clone(),
33 health: m.healthcheck.as_ref().and_then(Health::of),
34 }
35 }
36
37 #[must_use]
39 pub fn is_empty(&self) -> bool {
40 self.command.is_none() && self.working_dir.is_none() && self.health.is_none()
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Health {
47 pub test: HealthTest,
49 pub interval: u32,
51 pub timeout: u32,
53 pub retries: u32,
55 pub start_period: u32,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum HealthTest {
62 Command(CommandLine),
64 Http {
66 port: u16,
68 path: String,
70 },
71}
72
73impl Health {
74 #[must_use]
76 pub fn of(h: &Healthcheck) -> Option<Health> {
77 let test = match (&h.test, &h.http) {
78 (Some(t), _) => HealthTest::Command(t.clone()),
79 (None, Some(http)) => HealthTest::Http {
80 port: http.port,
81 path: http.path.clone(),
82 },
83 (None, None) => return None,
84 };
85 let pace = |v: &Option<String>, default| v.as_deref().and_then(seconds).unwrap_or(default);
86 Some(Health {
87 test,
88 interval: pace(&h.interval, DEFAULT_INTERVAL),
89 timeout: pace(&h.timeout, DEFAULT_TIMEOUT),
90 retries: h.retries.unwrap_or(DEFAULT_RETRIES),
91 start_period: pace(&h.start_period, DEFAULT_START_PERIOD),
92 })
93 }
94
95 #[must_use]
99 pub fn shell_test(&self, wget: &str) -> String {
100 match &self.test {
101 HealthTest::Command(c) => c.shell_line(),
102 HealthTest::Http { port, path } => {
103 format!("{wget} -q -O /dev/null -T {} http://127.0.0.1:{port}{path}", self.timeout)
104 }
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use isoloom_spec::model::{CommandLine, Healthcheck, HttpCheck};
112
113 use super::{Health, HealthTest, Process};
114
115 #[test]
116 fn a_process_is_empty_until_one_of_its_parts_is_set() {
117 assert!(Process::default().is_empty());
118 let health = Health {
119 test: HealthTest::Command(CommandLine::Shell("true".into())),
120 interval: 1,
121 timeout: 1,
122 retries: 1,
123 start_period: 0,
124 };
125 let one_each = [
126 Process {
127 command: Some(CommandLine::Shell("app".into())),
128 ..Process::default()
129 },
130 Process {
131 working_dir: Some("/srv".into()),
132 ..Process::default()
133 },
134 Process {
135 health: Some(health),
136 ..Process::default()
137 },
138 ];
139 for p in one_each {
140 assert!(!p.is_empty(), "{p:?}");
141 }
142 }
143
144 #[test]
145 fn the_pace_defaults_to_the_port_probes() {
146 let h = Healthcheck {
147 test: None,
148 http: Some(HttpCheck {
149 port: 8080,
150 path: "/healthz".into(),
151 }),
152 interval: Some("1m".into()),
153 timeout: None,
154 retries: None,
155 start_period: Some("0s".into()),
156 };
157 let health = Health::of(&h).expect("a test");
158 assert_eq!((health.interval, health.timeout, health.retries, health.start_period), (60, 3, 60, 0));
159 assert_eq!(
160 health.test,
161 HealthTest::Http {
162 port: 8080,
163 path: "/healthz".into()
164 }
165 );
166 assert_eq!(health.shell_test("wget"), "wget -q -O /dev/null -T 3 http://127.0.0.1:8080/healthz");
167 let cmd = Health {
168 test: HealthTest::Command(CommandLine::Exec(vec!["pg_isready".into()])),
169 ..health
170 };
171 assert_eq!(cmd.shell_test("wget"), "'pg_isready'");
172 }
173}