1use std::cmp::{
2 max,
3 min,
4 Ord,
5 Ordering,
6};
7
8use tracing::*;
9
10use super::*;
11use crate::prelude::*;
12
13impl PodLifecycleData {
38 fn new(start_ts: Option<i64>, end_ts: Option<i64>) -> PodLifecycleData {
39 match (start_ts, end_ts) {
40 (None, _) => PodLifecycleData::Empty,
41 (Some(ts), None) => PodLifecycleData::Running(ts),
42 (Some(start), Some(end)) => PodLifecycleData::Finished(start, end),
43 }
44 }
45
46 pub fn new_for(pod: &corev1::Pod) -> anyhow::Result<PodLifecycleData> {
47 let (mut earliest_start_ts, mut latest_end_ts) = (None, None);
48 let mut terminated_container_count = 0;
49
50 let pod_status = pod.status()?;
51 if let Some(cstats) = pod_status.init_container_statuses.as_ref() {
52 for (container, state) in cstats.iter().filter_map(|s| Some((&s.name, s.state.as_ref()?))) {
53 let (start_ts, end_ts) = get_start_end_ts(pod, container, state);
54 earliest_start_ts = min_some(start_ts, earliest_start_ts);
55 latest_end_ts = max(latest_end_ts, end_ts);
56 }
57 }
58
59 if let Some(cstats) = pod_status.container_statuses.as_ref() {
60 for (container, state) in cstats.iter().filter_map(|s| Some((&s.name, s.state.as_ref()?))) {
61 let (start_ts, end_ts) = get_start_end_ts(pod, container, state);
62 earliest_start_ts = min_some(start_ts, earliest_start_ts);
63
64 if end_ts.is_some() {
65 terminated_container_count += 1;
66 }
67 latest_end_ts = max(latest_end_ts, end_ts);
68 }
69 }
70
71 if terminated_container_count != pod.spec()?.containers.len() {
77 latest_end_ts = None;
78 }
79 Ok(PodLifecycleData::new(earliest_start_ts, latest_end_ts))
80 }
81
82 pub fn end_ts(&self) -> Option<i64> {
83 match self {
84 &PodLifecycleData::Finished(_, ts) => Some(ts),
85 _ => None,
86 }
87 }
88
89 pub fn start_ts(&self) -> Option<i64> {
90 match *self {
91 PodLifecycleData::Running(ts) => Some(ts),
92 PodLifecycleData::Finished(ts, _) => Some(ts),
93 _ => None,
94 }
95 }
96
97 pub fn overlaps(&self, start_ts: i64, end_ts: i64) -> bool {
98 match *self {
102 PodLifecycleData::Running(ts) => ts < end_ts,
103 PodLifecycleData::Finished(s, e) => (start_ts <= s && s < end_ts) || (start_ts <= e && e < end_ts),
104 _ => false,
105 }
106 }
107
108 pub fn guess_finished_lifecycle(
109 pod: &corev1::Pod,
110 current_lifecycle_data: &PodLifecycleData,
111 now: i64,
112 ) -> anyhow::Result<PodLifecycleData> {
113 let new_lifecycle_data = PodLifecycleData::new_for(pod).unwrap_or(PodLifecycleData::Empty);
114
115 match new_lifecycle_data {
116 PodLifecycleData::Finished(..) => Ok(new_lifecycle_data),
117 PodLifecycleData::Running(start_ts) => Ok(PodLifecycleData::Finished(start_ts, now)),
118 PodLifecycleData::Empty => {
119 let start_ts = if let Some(ts) = current_lifecycle_data.start_ts() {
120 ts
121 } else if let Some(t) = pod.creation_timestamp() {
122 t.0.timestamp()
123 } else {
124 bail!("could not determine final pod lifecycle for {}", pod.namespaced_name());
125 };
126 Ok(PodLifecycleData::Finished(start_ts, now))
127 },
128 }
129 }
130
131 pub fn empty(&self) -> bool {
132 self == PodLifecycleData::Empty
133 }
134
135 pub fn running(&self) -> bool {
136 matches!(self, PodLifecycleData::Running(_))
137 }
138
139 pub fn finished(&self) -> bool {
140 matches!(self, PodLifecycleData::Finished(..))
141 }
142}
143
144impl PartialOrd for PodLifecycleData {
160 fn partial_cmp(&self, other: &PodLifecycleData) -> Option<Ordering> {
161 match self {
162 PodLifecycleData::Empty => {
163 if !other.empty() {
164 Some(Ordering::Less)
165 } else {
166 Some(Ordering::Equal)
167 }
168 },
169 PodLifecycleData::Running(ts) => match other {
170 PodLifecycleData::Empty => Some(Ordering::Greater),
171 PodLifecycleData::Running(other_ts) => {
172 if ts == other_ts {
173 Some(Ordering::Equal)
174 } else {
175 None
176 }
177 },
178 PodLifecycleData::Finished(..) => Some(Ordering::Less),
179 },
180 PodLifecycleData::Finished(sts, ets) => match other {
181 PodLifecycleData::Empty => Some(Ordering::Greater),
182 PodLifecycleData::Running(other_ts) => {
183 if sts == other_ts {
184 Some(Ordering::Greater)
185 } else {
186 None
187 }
188 },
189 PodLifecycleData::Finished(other_sts, other_ets) => {
190 if sts == other_sts && ets == other_ets {
191 Some(Ordering::Equal)
192 } else {
193 None
194 }
195 },
196 },
197 }
198 }
199}
200
201fn get_start_end_ts(pod: &corev1::Pod, container: &str, state: &corev1::ContainerState) -> (Option<i64>, Option<i64>) {
202 let start_ts = state.start_ts().unwrap_or_else(|err| {
203 warn!("could not find start_ts for container {container} in {}: {err:?}", pod.namespaced_name());
204 None
205 });
206 let end_ts = state.end_ts().unwrap_or_else(|err| {
207 warn!("could not find end_ts for container {container} in {}: {err:?}", pod.namespaced_name());
208 None
209 });
210
211 (start_ts, end_ts)
212}
213
214impl PartialEq<Option<&PodLifecycleData>> for PodLifecycleData {
215 fn eq(&self, other: &Option<&PodLifecycleData>) -> bool {
216 match self {
217 PodLifecycleData::Empty => other.is_none() || other.as_ref().is_some_and(|plt| plt.empty()),
218 _ => other.as_ref().is_some_and(|plt| plt == self),
219 }
220 }
221}
222
223impl PartialOrd<Option<&PodLifecycleData>> for PodLifecycleData {
224 fn partial_cmp(&self, other: &Option<&PodLifecycleData>) -> Option<Ordering> {
225 match self {
226 PodLifecycleData::Empty => other.as_ref().map_or(Some(Ordering::Equal), |o| self.partial_cmp(o)),
227 _ => other.as_ref().map_or(Some(Ordering::Greater), |o| self.partial_cmp(o)),
228 }
229 }
230}
231
232pub fn min_some<T: Ord>(o1: Option<T>, o2: Option<T>) -> Option<T> {
240 if o1.is_none() {
241 o2
242 } else if o2.is_none() {
243 o1
244 } else {
245 min(o1, o2)
246 }
247}
248
249#[cfg(test)]
250#[cfg_attr(coverage, coverage(off))]
251mod test {
252 use sk_testutils::*;
253
254 use super::*;
255
256 #[rstest]
257 #[case::both_none(None, None, None)]
258 #[case::left_some(Some(1), None, Some(1))]
259 #[case::right_some(None, Some(1), Some(1))]
260 #[case::both_some(Some(2), Some(1), Some(1))]
261 fn test_min_some(#[case] o1: Option<i32>, #[case] o2: Option<i32>, #[case] expected: Option<i32>) {
262 assert_eq!(min_some(o1, o2), expected);
263 }
264}