orca_core/runtime.rs
1//! Core runtime abstraction for workload execution.
2
3use std::collections::HashMap;
4use std::pin::Pin;
5use std::time::Duration;
6
7use async_trait::async_trait;
8use tokio::io::AsyncRead;
9
10use crate::error::Result;
11use crate::types::{ResourceStats, WorkloadSpec, WorkloadStatus};
12
13/// Helper trait for downcasting trait objects to concrete types.
14pub trait AsAny: Send + Sync {
15 fn as_any(&self) -> &dyn std::any::Any;
16}
17
18/// Opaque handle returned by a runtime after creating a workload.
19#[derive(Debug, Clone)]
20pub struct WorkloadHandle {
21 /// Runtime-specific identifier (container ID, Wasm instance ID, etc.)
22 pub runtime_id: String,
23 /// Human-friendly name.
24 pub name: String,
25 /// Runtime-specific metadata (e.g., host_port, container_ip).
26 pub metadata: HashMap<String, String>,
27}
28
29/// Options for log retrieval.
30#[derive(Debug, Clone, Default)]
31pub struct LogOpts {
32 /// Follow log output (stream).
33 pub follow: bool,
34 /// Number of recent lines to return.
35 pub tail: Option<u64>,
36 /// Only return logs since this timestamp.
37 pub since: Option<chrono::DateTime<chrono::Utc>>,
38 /// Include timestamps in log output.
39 pub timestamps: bool,
40}
41
42/// Result of executing a command inside a workload.
43#[derive(Debug)]
44pub struct ExecResult {
45 /// Process exit code.
46 pub exit_code: i32,
47 /// Standard output bytes.
48 pub stdout: Vec<u8>,
49 /// Standard error bytes.
50 pub stderr: Vec<u8>,
51}
52
53/// A stream of log bytes.
54pub type LogStream = Pin<Box<dyn AsyncRead + Send>>;
55
56/// The core runtime abstraction. Both container and Wasm runtimes implement this.
57#[async_trait]
58pub trait Runtime: AsAny + Send + Sync + 'static {
59 /// Human-readable name of this runtime (e.g., "container", "wasm").
60 fn name(&self) -> &str;
61
62 /// Create a new workload from the given spec. Does not start it.
63 async fn create(&self, spec: &WorkloadSpec) -> Result<WorkloadHandle>;
64
65 /// Start a previously created workload.
66 async fn start(&self, handle: &WorkloadHandle) -> Result<()>;
67
68 /// Stop a running workload, waiting up to `timeout` for graceful shutdown.
69 async fn stop(&self, handle: &WorkloadHandle, timeout: Duration) -> Result<()>;
70
71 /// Remove a stopped workload and clean up resources.
72 async fn remove(&self, handle: &WorkloadHandle) -> Result<()>;
73
74 /// Get the current status of a workload.
75 async fn status(&self, handle: &WorkloadHandle) -> Result<WorkloadStatus>;
76
77 /// Stream logs from a workload.
78 async fn logs(&self, handle: &WorkloadHandle, opts: &LogOpts) -> Result<LogStream>;
79
80 /// Execute a command inside a running workload.
81 async fn exec(&self, handle: &WorkloadHandle, cmd: &[String]) -> Result<ExecResult>;
82
83 /// Get current resource usage stats.
84 async fn stats(&self, handle: &WorkloadHandle) -> Result<ResourceStats>;
85
86 /// Resolve the host-accessible port for a workload after it has been started.
87 ///
88 /// Returns `None` if the runtime does not expose ports or the workload has no port mapping.
89 /// Default implementation returns `None`.
90 async fn resolve_host_port(
91 &self,
92 handle: &WorkloadHandle,
93 _container_port: u16,
94 ) -> Result<Option<u16>> {
95 Ok(handle
96 .metadata
97 .get("host_port")
98 .and_then(|p| p.parse().ok()))
99 }
100
101 /// Resolve the container's network address (ip:port) on its Docker network.
102 ///
103 /// This allows the proxy to route directly to the container IP
104 /// without going through host port mappings.
105 /// Returns `None` if not applicable (e.g., Wasm workloads).
106 async fn resolve_container_address(
107 &self,
108 _handle: &WorkloadHandle,
109 _container_port: u16,
110 _network: &str,
111 ) -> Result<Option<String>> {
112 Ok(None)
113 }
114}