Skip to main content

synd_runtime/
runtime.rs

1use std::{path::PathBuf, time::Duration};
2
3use crate::{
4    DaemonControl, DaemonLaunchConfig, DaemonLaunchLog, PlacementSummary, Result, RuntimeDatabase,
5    Session, SessionConfig, SessionRequirements,
6    acquisition::SessionAcquisition,
7    placement::{PlacementEnvironment, PlacementResolver, PlacementSpec},
8};
9
10#[derive(Debug, Clone)]
11pub struct Runtime {
12    config: Config,
13    placement: PlacementSpec,
14}
15
16impl Runtime {
17    pub fn try_new(config: Config) -> Result<Self> {
18        let placement =
19            PlacementResolver::with_environment(config.placement_environment()).resolve(&config)?;
20
21        Ok(Self { config, placement })
22    }
23
24    pub fn config(&self) -> &Config {
25        &self.config
26    }
27
28    pub(crate) fn placement(&self) -> &PlacementSpec {
29        &self.placement
30    }
31
32    pub fn placement_summary(&self) -> PlacementSummary {
33        PlacementSummary::from_placement(&self.placement)
34    }
35
36    pub async fn acquire_session(&self) -> Result<Session> {
37        SessionAcquisition::new(self).acquire().await
38    }
39
40    pub fn daemon(&self) -> DaemonControl<'_> {
41        DaemonControl::new(self)
42    }
43}
44
45#[derive(Debug, Clone)]
46pub struct Config {
47    database: RuntimeDatabase,
48    client: ApiClientConfig,
49    session: SessionConfig,
50    daemon: DaemonLaunchConfig,
51    requirements: SessionRequirements,
52    placement_environment: PlacementEnvironment,
53}
54
55impl Config {
56    pub fn new(database: RuntimeDatabase) -> Self {
57        Self {
58            database,
59            client: ApiClientConfig::default(),
60            session: SessionConfig::default(),
61            daemon: DaemonLaunchConfig::default(),
62            requirements: SessionRequirements::default(),
63            placement_environment: PlacementEnvironment::capture(),
64        }
65    }
66
67    #[must_use]
68    pub fn with_api_client(mut self, client: ApiClientConfig) -> Self {
69        self.client = client;
70        self
71    }
72
73    #[must_use]
74    pub fn with_api_timeout(
75        self,
76        request_timeout: Duration,
77        user_agent: impl Into<String>,
78    ) -> Self {
79        self.with_api_client(ApiClientConfig::new(request_timeout, user_agent))
80    }
81
82    #[must_use]
83    pub fn with_session(mut self, session: SessionConfig) -> Self {
84        self.session = session;
85        self
86    }
87
88    #[must_use]
89    pub fn with_session_timeout(self, acquire_timeout: Duration) -> Self {
90        self.with_session(SessionConfig::new(acquire_timeout))
91    }
92
93    #[must_use]
94    pub fn with_daemon_launch(mut self, daemon: DaemonLaunchConfig) -> Self {
95        self.daemon = daemon;
96        self
97    }
98
99    #[must_use]
100    pub fn with_daemon_log(mut self, log: impl Into<PathBuf>) -> Self {
101        self.daemon = self.daemon.with_log(DaemonLaunchLog::file(log));
102        self
103    }
104
105    #[must_use]
106    pub fn with_daemon_session_lease_duration(mut self, lease_duration: Duration) -> Self {
107        self.daemon = self.daemon.with_session_lease_duration(lease_duration);
108        self
109    }
110
111    #[must_use]
112    pub fn with_daemon_session_idle_shutdown_grace(
113        mut self,
114        idle_shutdown_grace: Duration,
115    ) -> Self {
116        self.daemon = self
117            .daemon
118            .with_session_idle_shutdown_grace(idle_shutdown_grace);
119        self
120    }
121
122    #[must_use]
123    pub fn with_runtime_root(mut self, root: impl Into<PathBuf>) -> Self {
124        self.placement_environment = PlacementEnvironment::from_root(root);
125        self
126    }
127
128    #[must_use]
129    pub fn with_requirements(mut self, requirements: SessionRequirements) -> Self {
130        self.requirements = requirements;
131        self
132    }
133
134    pub fn database(&self) -> &RuntimeDatabase {
135        &self.database
136    }
137
138    pub fn client(&self) -> &ApiClientConfig {
139        &self.client
140    }
141
142    pub fn session(&self) -> &SessionConfig {
143        &self.session
144    }
145
146    pub fn daemon(&self) -> &DaemonLaunchConfig {
147        &self.daemon
148    }
149
150    pub fn requirements(&self) -> &SessionRequirements {
151        &self.requirements
152    }
153
154    pub(crate) fn placement_environment(&self) -> PlacementEnvironment {
155        self.placement_environment.clone()
156    }
157
158    #[cfg(test)]
159    pub(crate) fn with_placement_environment(
160        mut self,
161        placement_environment: PlacementEnvironment,
162    ) -> Self {
163        self.placement_environment = placement_environment;
164        self
165    }
166}
167
168/// Configuration for `synd_api` client
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct ApiClientConfig {
171    request_timeout: Duration,
172    user_agent: String,
173}
174
175impl ApiClientConfig {
176    pub fn new(request_timeout: Duration, user_agent: impl Into<String>) -> Self {
177        Self {
178            request_timeout,
179            user_agent: user_agent.into(),
180        }
181    }
182
183    pub fn request_timeout(&self) -> Duration {
184        self.request_timeout
185    }
186
187    pub fn user_agent(&self) -> &str {
188        &self.user_agent
189    }
190}
191
192impl Default for ApiClientConfig {
193    fn default() -> Self {
194        Self::new(
195            Duration::from_secs(30),
196            concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
197        )
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use std::time::Duration;
204
205    use crate::{
206        DaemonExecutable, DaemonLaunchConfig, DaemonLaunchLog, Runtime, RuntimeConfig,
207        RuntimeDatabase,
208    };
209
210    mod daemon_launch {
211        use super::*;
212
213        #[test]
214        fn keeps_config() -> crate::Result<()> {
215            let tempdir = tempfile::tempdir()?;
216            let config =
217                RuntimeConfig::new(RuntimeDatabase::sqlite(tempdir.path().join("synd.db")))
218                    .with_api_timeout(Duration::from_secs(5), "synd-runtime-test")
219                    .with_session_timeout(Duration::from_secs(5))
220                    .with_daemon_launch(DaemonLaunchConfig::default());
221
222            let runtime = Runtime::try_new(config.clone())?;
223
224            assert_eq!(runtime.config().daemon(), config.daemon());
225            Ok(())
226        }
227    }
228
229    mod daemon_log {
230        use super::*;
231
232        #[test]
233        fn keeps_executable() -> crate::Result<()> {
234            let tempdir = tempfile::tempdir()?;
235            let log = tempdir.path().join("daemon.log");
236            let config =
237                RuntimeConfig::new(RuntimeDatabase::sqlite(tempdir.path().join("synd.db")))
238                    .with_daemon_launch(DaemonLaunchConfig::new(
239                        DaemonExecutable::path("/usr/bin/custom-synd"),
240                        DaemonLaunchLog::file(tempdir.path().join("old.log")),
241                    ))
242                    .with_daemon_log(log.clone());
243
244            assert_eq!(
245                config.daemon(),
246                &DaemonLaunchConfig::new(
247                    DaemonExecutable::path("/usr/bin/custom-synd"),
248                    DaemonLaunchLog::file(log),
249                )
250            );
251            Ok(())
252        }
253    }
254}