a3s_box_runtime/local_execution/
mod.rs1mod api;
4mod backend;
5mod create;
6#[cfg(test)]
7mod inspection_cancellation_tests;
8mod lifecycle_lock;
9pub use lifecycle_lock::{
10 acquire_blocking as acquire_execution_lifecycle_lock, ExecutionLifecycleLock,
11};
12mod logs;
13mod oci_backend;
14#[cfg(feature = "vm")]
15mod oci_log_projection;
16#[cfg(feature = "vm")]
17mod oci_migration;
18#[cfg(all(feature = "vm", target_os = "linux"))]
19mod oci_owner;
20#[cfg(feature = "vm")]
21pub(crate) mod oci_portable_rootfs;
22#[cfg(feature = "vm")]
23mod oci_production;
24mod oci_session;
25mod operations;
26mod port;
27mod prepared_rootfs;
28mod record;
29mod recovery;
30mod remove;
31mod resources;
32mod restart;
33mod router;
34#[cfg(test)]
35mod router_tests;
36#[cfg(unix)]
37mod session;
38mod session_support;
39#[cfg(not(unix))]
40mod session_unsupported;
41mod snapshot;
42mod store;
43mod support;
44#[cfg(all(feature = "vm", target_os = "linux"))]
45mod transient_registry_auth;
46#[cfg(feature = "vm")]
47mod vm_backend;
48#[cfg(feature = "vm")]
49mod vm_process;
50
51use std::path::PathBuf;
52use std::sync::Arc;
53
54use a3s_box_core::{
55 ExecutionGeneration, ExecutionId, ExecutionIsolation, ExecutionManagerError,
56 ExecutionManagerResult,
57};
58
59pub use backend::{
60 LocalExecutionBackend, LocalExecutionHandle, LocalExecutionObservation,
61 LocalExecutionResourcePlan, LocalExecutionTermination,
62};
63pub use oci_backend::{
64 oci_isolation_request, OciBundlePreparationContext, OciBundleProvider, OciLifecycleAdapter,
65 OciLocalExecutionBackend, OciPreparedExecution, OciRuntimeBinding, OciRuntimeEndpoint,
66 OciRuntimeLaunch, OCI_RUNTIME_BINDING_SCHEMA_VERSION,
67};
68#[cfg(feature = "vm")]
69pub use oci_migration::{NativeLinuxOciMigrationConfig, WindowsWhpxOciMigrationConfig};
70#[cfg(feature = "vm")]
71pub use oci_production::{NativeLinuxOciBundleProvider, WindowsWhpxOciBundleProvider};
72use record::{build_managed_record, status_from_record};
73pub use router::{LocalExecutionBackendRouter, OciMigrationPolicy};
74use store::RuntimeUpdate;
75#[cfg(all(feature = "vm", target_os = "linux"))]
76pub(crate) use transient_registry_auth::{TransientRegistryAuthBroker, TransientRegistryAuthLease};
77#[cfg(feature = "vm")]
78pub use vm_backend::VmLocalExecutionBackend;
79
80use crate::{BoxRecord, ManagedExecutionOperation, ManagedExecutionState, ManagedExecutionStore};
81
82#[derive(Clone)]
84pub struct LocalExecutionManager {
85 store: ManagedExecutionStore,
86 home_dir: PathBuf,
87 backend: Arc<dyn LocalExecutionBackend>,
88}
89
90impl LocalExecutionManager {
91 pub fn new(
92 state_path: impl Into<PathBuf>,
93 home_dir: impl Into<PathBuf>,
94 backend: Arc<dyn LocalExecutionBackend>,
95 ) -> Self {
96 Self {
97 store: ManagedExecutionStore::new(state_path),
98 home_dir: home_dir.into(),
99 backend,
100 }
101 }
102
103 pub fn state_path(&self) -> &std::path::Path {
104 self.store.path()
105 }
106
107 pub async fn preflight_isolation(
111 &self,
112 isolation: ExecutionIsolation,
113 ) -> ExecutionManagerResult<()> {
114 self.backend.preflight_isolation(isolation).await
115 }
116
117 pub(super) async fn require_running_record(
118 &self,
119 execution_id: &ExecutionId,
120 generation: ExecutionGeneration,
121 ) -> ExecutionManagerResult<BoxRecord> {
122 self.require_live_record(execution_id, generation, false)
123 .await
124 }
125
126 pub(super) async fn require_observable_record(
127 &self,
128 execution_id: &ExecutionId,
129 generation: ExecutionGeneration,
130 ) -> ExecutionManagerResult<BoxRecord> {
131 self.require_live_record(execution_id, generation, true)
132 .await
133 }
134
135 async fn require_live_record(
136 &self,
137 execution_id: &ExecutionId,
138 generation: ExecutionGeneration,
139 allow_paused: bool,
140 ) -> ExecutionManagerResult<BoxRecord> {
141 let record = self
142 .get(execution_id)
143 .await?
144 .ok_or_else(|| ExecutionManagerError::NotFound(execution_id.clone()))?;
145 support::require_generation(&record, execution_id, generation)?;
146 let state = support::managed_state(&record)?;
147 if state != ManagedExecutionState::Running
148 && !(allow_paused && state == ManagedExecutionState::Paused)
149 {
150 return Err(ExecutionManagerError::Conflict {
151 execution_id: execution_id.clone(),
152 message: if allow_paused {
153 "execution is neither running nor paused".to_string()
154 } else {
155 "execution is not running".to_string()
156 },
157 });
158 }
159 if let Some(binding) = record
160 .managed_execution
161 .as_ref()
162 .and_then(|metadata| metadata.oci_runtime.as_ref())
163 {
164 binding.validate_for(execution_id)?;
165 return Ok(record);
166 }
167 if record.exec_socket_path.as_os_str().is_empty() {
168 return Err(ExecutionManagerError::Internal(format!(
169 "execution {execution_id} has no exec endpoint"
170 )));
171 }
172 #[cfg(target_os = "linux")]
173 {
174 let pid = record
175 .pid
176 .ok_or_else(|| ExecutionManagerError::NotFound(execution_id.clone()))?;
177 if !crate::process::is_process_alive_with_identity(pid, record.pid_start_time) {
178 return Err(ExecutionManagerError::NotFound(execution_id.clone()));
179 }
180 }
181 Ok(record)
182 }
183
184 pub(super) async fn require_same_runtime(
185 &self,
186 bound: &BoxRecord,
187 execution_id: &ExecutionId,
188 generation: ExecutionGeneration,
189 ) -> ExecutionManagerResult<()> {
190 let current = self
191 .require_running_record(execution_id, generation)
192 .await?;
193 Self::validate_same_runtime(bound, ¤t, execution_id)
194 }
195
196 pub(super) async fn require_same_observable_runtime(
197 &self,
198 bound: &BoxRecord,
199 execution_id: &ExecutionId,
200 generation: ExecutionGeneration,
201 ) -> ExecutionManagerResult<()> {
202 let current = self
203 .require_observable_record(execution_id, generation)
204 .await?;
205 Self::validate_same_runtime(bound, ¤t, execution_id)
206 }
207
208 fn validate_same_runtime(
209 bound: &BoxRecord,
210 current: &BoxRecord,
211 execution_id: &ExecutionId,
212 ) -> ExecutionManagerResult<()> {
213 if current.pid != bound.pid
214 || current.pid_start_time != bound.pid_start_time
215 || current.exec_socket_path != bound.exec_socket_path
216 || current
217 .managed_execution
218 .as_ref()
219 .and_then(|metadata| metadata.oci_runtime.as_ref())
220 != bound
221 .managed_execution
222 .as_ref()
223 .and_then(|metadata| metadata.oci_runtime.as_ref())
224 {
225 return Err(ExecutionManagerError::Conflict {
226 execution_id: execution_id.clone(),
227 message: "runtime generation changed while binding its execution session"
228 .to_string(),
229 });
230 }
231 Ok(())
232 }
233
234 #[cfg(feature = "vm")]
235 pub fn with_vm_backend(state_path: impl Into<PathBuf>, home_dir: impl Into<PathBuf>) -> Self {
236 let home_dir = home_dir.into();
237 Self::new(
238 state_path,
239 home_dir.clone(),
240 Arc::new(VmLocalExecutionBackend::new(home_dir)),
241 )
242 }
243
244 #[cfg(feature = "vm")]
248 pub fn with_oci_migration_backend(
249 state_path: impl Into<PathBuf>,
250 home_dir: impl Into<PathBuf>,
251 oci_backend: Arc<dyn LocalExecutionBackend>,
252 policy: OciMigrationPolicy,
253 ) -> Self {
254 Self::with_oci_migration_backend_and_pull_progress(
255 state_path,
256 home_dir,
257 oci_backend,
258 policy,
259 None,
260 )
261 }
262
263 #[cfg(feature = "vm")]
264 fn with_oci_migration_backend_and_pull_progress(
265 state_path: impl Into<PathBuf>,
266 home_dir: impl Into<PathBuf>,
267 oci_backend: Arc<dyn LocalExecutionBackend>,
268 policy: OciMigrationPolicy,
269 pull_progress_fn: Option<crate::PullProgressFn>,
270 ) -> Self {
271 let home_dir = home_dir.into();
272 let mut legacy_backend = VmLocalExecutionBackend::new(home_dir.clone());
273 if let Some(pull_progress_fn) = pull_progress_fn {
274 legacy_backend = legacy_backend.with_pull_progress_fn(pull_progress_fn);
275 }
276 let legacy: Arc<dyn LocalExecutionBackend> = Arc::new(legacy_backend);
277 let router = LocalExecutionBackendRouter::new(legacy, oci_backend, policy);
278 Self::new(state_path, home_dir, Arc::new(router))
279 }
280}
281
282#[cfg(test)]
283mod tests;