Skip to main content

sk_core/
events.rs

1use std::sync::Arc;
2
3use anyhow::bail;
4use async_trait::async_trait;
5use k8s_openapi::api::batch::v1 as batchv1;
6use kube::Resource;
7use kube::runtime::events;
8use kube::runtime::events::{
9    Event,
10    EventType,
11};
12#[cfg(feature = "mock")]
13use mockall::automock;
14
15use crate::prelude::*;
16
17#[cfg_attr(feature = "mock", automock)]
18#[async_trait]
19trait EventSendable {
20    async fn send_event(&self, event: Event, object_ref: &corev1::ObjectReference) -> EmptyResult;
21}
22
23#[derive(Clone)]
24struct EventSender {
25    recorder: events::Recorder,
26}
27
28#[async_trait]
29impl EventSendable for EventSender {
30    async fn send_event(&self, event: Event, object_ref: &corev1::ObjectReference) -> EmptyResult {
31        Ok(self.recorder.publish(&event, object_ref).await?)
32    }
33}
34
35#[derive(Clone)]
36pub struct SkEventRecorder {
37    sender: Arc<dyn EventSendable + Send + Sync>,
38    sim_object_ref: Option<corev1::ObjectReference>,
39}
40
41
42impl SkEventRecorder {
43    pub fn new(client: kube::Client, controller: String) -> SkEventRecorder {
44        let reporter = events::Reporter { controller, instance: None };
45        let recorder = events::Recorder::new(client, reporter);
46        SkEventRecorder {
47            sender: Arc::new(EventSender { recorder }),
48            sim_object_ref: None,
49        }
50    }
51
52    pub fn with_sim(&self, sim: &Simulation) -> Self {
53        let mut rec = self.clone();
54        rec.sim_object_ref = Some(sim.object_ref(&()).clone());
55        rec
56    }
57
58    pub async fn send_driver_created_event(&self, job: &batchv1::Job) -> EmptyResult {
59        self.send_event(Event {
60            type_: EventType::Normal,
61            reason: "SuccessfulDriverCreate".into(),
62            note: Some(format!("Created driver job: {}", job.namespaced_name())),
63            action: "SimulationPrerequisitesCreated".into(),
64            secondary: None,
65        })
66        .await
67    }
68
69    pub async fn send_driver_pod_failed_event(&self) -> EmptyResult {
70        self.send_event(Event {
71            type_: EventType::Normal,
72            reason: "SimulationDriverFailed".into(),
73            note: Some("Simulation driver pod failed".into()),
74            action: "SimulationDriverRunning".into(),
75            secondary: None,
76        })
77        .await
78    }
79
80    // exit_code will be None if the hook was terminated by a signal
81    pub async fn send_hook_failed_event(
82        &self,
83        hook_type: &str,
84        hook_cmd_str: &str,
85        exit_code: Option<i32>,
86    ) -> EmptyResult {
87        self.send_event(Event {
88            type_: EventType::Warning,
89            reason: format!("{hook_type}HookFailed"),
90            note: Some(format!(
91                "Simulation hook `{hook_cmd_str}` failed with exit code: {}",
92                exit_code.map(|c| format!("{c}")).unwrap_or("<unknown>".into())
93            )),
94            action: format!("{hook_type}HookExecuted"),
95            secondary: None,
96        })
97        .await
98    }
99
100    pub async fn send_hooks_succeeded_event(&self, hook_type: &str) -> EmptyResult {
101        self.send_event(Event {
102            type_: EventType::Normal,
103            reason: format!("{hook_type}HooksSucceeded"),
104            note: Some("All hooks succeeded".into()),
105            action: format!("{hook_type}HookExecuted"),
106            secondary: None,
107        })
108        .await
109    }
110
111    pub async fn send_sim_blocked_event(&self) -> EmptyResult {
112        self.send_event(Event {
113            type_: EventType::Normal,
114            reason: "Blocked".into(),
115            note: Some("Simulation is blocked from running".into()),
116            action: "OtherSimulationRunning".into(),
117            secondary: None,
118        })
119        .await
120    }
121
122    pub async fn send_sim_cleanup_failed_event(&self) -> EmptyResult {
123        self.send_event(Event {
124            type_: EventType::Warning,
125            reason: "CleanupFailed".into(),
126            note: Some("Simulation cleanup failed; there may be dangling resources".into()),
127            action: "SimulationCleanupInitiated".into(),
128            secondary: None,
129        })
130        .await
131    }
132
133    pub async fn send_sim_started_event(&self, sim_name: &str) -> EmptyResult {
134        self.send_event(Event {
135            type_: EventType::Normal,
136            reason: "SimulationStarted".into(),
137            note: Some(format!("Started running simulation: {}", sim_name)),
138            action: "NewSimulationResourceCreated".into(),
139            secondary: None,
140        })
141        .await
142    }
143
144    pub async fn send_waiting_for_metrics_event(&self) -> EmptyResult {
145        self.send_event(Event {
146            type_: EventType::Normal,
147            reason: "WaitingForMetrics".into(),
148            note: Some("Monitoring pod(s) are not Ready".into()),
149            action: "PrometheusResourceCreated".into(),
150            secondary: None,
151        })
152        .await
153    }
154
155    pub async fn send_waiting_for_secret_event(&self) -> EmptyResult {
156        self.send_event(Event {
157            type_: EventType::Normal,
158            reason: "WaitingForDriverSecret".into(),
159            note: Some("The driver certificate secret is not Created".into()),
160            action: "CertificateRequestCreated".into(),
161            secondary: None,
162        })
163        .await
164    }
165
166    pub async fn send_waiting_for_webhook_cert_event(&self) -> EmptyResult {
167        self.send_event(Event {
168            type_: EventType::Normal,
169            reason: "WaitingForWebhookCertBundle".into(),
170            note: Some("The driver webhook certificate bundle is not injected".into()),
171            action: "CertificateRequestCreated".into(),
172            secondary: None,
173        })
174        .await
175    }
176
177    async fn send_event(&self, event: Event) -> EmptyResult {
178        let Some(object_ref) = &self.sim_object_ref else {
179            bail!("Simulation object reference empty; cannot send event");
180        };
181        self.sender.send_event(event, object_ref).await
182    }
183}
184
185#[cfg(feature = "mock")]
186impl SkEventRecorder {
187    pub fn mock() -> SkEventRecorder {
188        let mut sender = MockEventSendable::new();
189        sender.expect_send_event().returning(|_, _| Ok(()));
190        SkEventRecorder {
191            sender: Arc::new(sender),
192            sim_object_ref: Some(corev1::ObjectReference::default()),
193        }
194    }
195}