fakecloud_lambda/runtime/backend.rs
1//! Pluggable Lambda execution backend abstraction.
2//!
3//! The facade in [`super::facade::LambdaRuntime`] owns the warm-pool
4//! bookkeeping, per-function startup serialization, and the HTTP
5//! invocation logic that's identical across backends. Backends only
6//! implement how to bring up a fresh runtime instance for a function
7//! (Docker container, Kubernetes pod, ...), how to tear it down, and
8//! optionally how to sweep instances left behind by a previous
9//! fakecloud process.
10
11use async_trait::async_trait;
12
13use crate::state::LambdaFunction;
14
15#[derive(Debug, thiserror::Error)]
16pub enum RuntimeError {
17 #[error("no code ZIP provided for function {0}")]
18 NoCodeZip(String),
19 #[error("unsupported runtime: {0}")]
20 UnsupportedRuntime(String),
21 #[error("container failed to start: {0}")]
22 ContainerStartFailed(String),
23 #[error("invocation failed: {0}")]
24 InvocationFailed(String),
25 #[error("ZIP extraction failed: {0}")]
26 ZipExtractionFailed(String),
27}
28
29/// Opaque per-backend identifier for a launched runtime instance. The
30/// facade hands this back to the backend on teardown so the backend can
31/// find the right resource to delete.
32#[derive(Debug, Clone)]
33pub enum BackendHandle {
34 Container { id: String },
35 Pod { namespace: String, name: String },
36}
37
38/// What [`LambdaBackend::launch`] returns. `endpoint` is the `host:port`
39/// the facade POSTs invocation payloads to; `handle` is the
40/// backend-specific identifier handed back on `terminate`.
41#[derive(Debug, Clone)]
42pub struct WarmInstance {
43 pub endpoint: String,
44 pub handle: BackendHandle,
45}
46
47/// Wrapper around an in-flight streaming invocation. Yields raw body
48/// chunks via [`Self::next_chunk`] until the RIE closes the response,
49/// at which point the final `Ok(None)` signals the caller to emit the
50/// terminal `InvokeComplete` frame.
51pub struct StreamingInvocation {
52 pub(crate) resp: reqwest::Response,
53}
54
55impl StreamingInvocation {
56 /// Read the next chunk of the function's response body. Returns
57 /// `Ok(None)` once the RIE has finished streaming. Buffered
58 /// handlers tend to deliver a single chunk; streaming handlers
59 /// deliver one chunk per `responseStream.write(...)` call.
60 pub async fn next_chunk(&mut self) -> Result<Option<bytes::Bytes>, RuntimeError> {
61 match self.resp.chunk().await {
62 Ok(Some(b)) => Ok(Some(b)),
63 Ok(None) => Ok(None),
64 Err(e) => Err(RuntimeError::InvocationFailed(e.to_string())),
65 }
66 }
67}
68
69#[async_trait]
70pub trait LambdaBackend: Send + Sync + 'static {
71 /// Short identifier surfaced via logs and introspection (e.g.
72 /// `"docker"`, `"podman"`, `"kubernetes"`).
73 fn name(&self) -> &str;
74
75 /// Launch a fresh runtime instance for `func` using the supplied
76 /// code (for zip-package functions) or `func.image_uri` (for image
77 /// package functions). `layers` are the attached layer ZIPs in
78 /// attach order. `deploy_id` is the facade-computed fingerprint
79 /// used to label resources so reaper logic can correlate.
80 async fn launch(
81 &self,
82 func: &LambdaFunction,
83 code_zip: Option<&[u8]>,
84 layers: &[Vec<u8>],
85 deploy_id: &str,
86 ) -> Result<WarmInstance, RuntimeError>;
87
88 /// Tear down one instance. Must be idempotent — the facade may call
89 /// this against an already-gone instance during cleanup races.
90 async fn terminate(&self, handle: &BackendHandle);
91
92 /// Sweep instances that belong to a previous fakecloud process so
93 /// their function names don't leak across restarts. Default no-op;
94 /// backends with out-of-process state should override.
95 async fn reap_stale(&self) {}
96
97 /// Optional hook: pre-warm the runtime image so the first `launch()`
98 /// for a function doesn't pay the cold-pull cost. Called in the
99 /// background after `CreateFunction` persists; backends that don't
100 /// benefit from pre-pulling (e.g. Kubernetes, which pulls images on
101 /// the scheduling node anyway) leave this as a no-op.
102 ///
103 /// `image` is the registry URI fetched from `runtime_to_image` for
104 /// Zip-package functions, or the user-supplied `ImageUri` (already
105 /// translated to the local registry if applicable) for Image-package
106 /// functions.
107 async fn prepull_image(&self, _image: &str) -> Result<(), RuntimeError> {
108 Ok(())
109 }
110}