Skip to main content

isothrum_runner/
hooks.rs

1//! The embedder's sidecars around lifecycle edges. The launcher's attack box is these hooks: it
2//! starts (or rejoins) after `up` and `resume`, stops before `park`, and goes before `down` so the
3//! run's network is free. A hook's failure is a warning event, never a failed operation.
4
5use isoloom_manifest::Manifest;
6
7use crate::BoxFuture;
8use crate::name::RunName;
9use crate::status::{NetworkView, OutputValue};
10
11/// Why a hook failed, in its own words.
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[error("{0}")]
14pub struct HookError(String);
15
16impl HookError {
17    /// A hook's failure, in its words.
18    pub fn new(message: impl Into<String>) -> HookError {
19        HookError(message.into())
20    }
21}
22
23impl From<String> for HookError {
24    fn from(message: String) -> HookError {
25        HookError(message)
26    }
27}
28
29impl From<&str> for HookError {
30    fn from(message: &str) -> HookError {
31        HookError(message.to_owned())
32    }
33}
34
35/// What a hook sees of the run.
36#[derive(Debug, Clone)]
37#[non_exhaustive]
38pub struct RunView<'a> {
39    /// The run's name.
40    pub name: &'a RunName,
41    /// The manifest.
42    pub manifest: &'a Manifest,
43    /// The run's networks, with their native names.
44    pub networks: &'a [NetworkView],
45    /// The run's outputs (empty before it is up).
46    pub outputs: &'a [OutputValue],
47}
48
49/// Hooks around a run's lifecycle edges; each defaults to doing nothing.
50pub trait Hooks: Send + Sync {
51    /// After a successful `up`.
52    fn after_up<'a>(&'a self, run: &'a RunView<'a>) -> BoxFuture<'a, Result<(), HookError>> {
53        let _ = run;
54        Box::pin(async { Ok(()) })
55    }
56
57    /// Before `down`.
58    fn before_down<'a>(&'a self, run: &'a RunView<'a>) -> BoxFuture<'a, Result<(), HookError>> {
59        let _ = run;
60        Box::pin(async { Ok(()) })
61    }
62
63    /// Before `park`.
64    fn before_park<'a>(&'a self, run: &'a RunView<'a>) -> BoxFuture<'a, Result<(), HookError>> {
65        let _ = run;
66        Box::pin(async { Ok(()) })
67    }
68
69    /// After a successful `resume`.
70    fn after_resume<'a>(&'a self, run: &'a RunView<'a>) -> BoxFuture<'a, Result<(), HookError>> {
71        let _ = run;
72        Box::pin(async { Ok(()) })
73    }
74}
75
76/// How long a hook may take: the embedder's sidecar is an external call like any other.
77const HOOK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(600);
78
79/// A lifecycle edge.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub(crate) enum Edge {
82    AfterUp,
83    BeforeDown,
84    BeforePark,
85    AfterResume,
86}
87
88impl Edge {
89    fn name(self) -> &'static str {
90        match self {
91            Edge::AfterUp => "after_up",
92            Edge::BeforeDown => "before_down",
93            Edge::BeforePark => "before_park",
94            Edge::AfterResume => "after_resume",
95        }
96    }
97
98    /// Runs `hooks` at this edge; each failure is returned with the hook's edge.
99    pub(crate) async fn run(self, hooks: &[std::sync::Arc<dyn Hooks>], run: &RunView<'_>) -> Vec<String> {
100        let mut failures = Vec::new();
101        for hook in hooks {
102            let call = match self {
103                Edge::AfterUp => hook.after_up(run),
104                Edge::BeforeDown => hook.before_down(run),
105                Edge::BeforePark => hook.before_park(run),
106                Edge::AfterResume => hook.after_resume(run),
107            };
108            match tokio::time::timeout(HOOK_TIMEOUT, call).await {
109                Ok(Ok(())) => {}
110                Ok(Err(e)) => failures.push(format!("{}: {e}", self.name())),
111                Err(_) => failures.push(format!("{}: no answer within {}s", self.name(), HOOK_TIMEOUT.as_secs())),
112            }
113        }
114        failures
115    }
116}