VM Integration: - Add compute module with offloadable operations support - Enable distributed execution for heavy VM operations - Support batch signature verification, merkle proofs, hashing - Add ComputeContext for managing compute cluster connections - Feature-gated behind 'compute' flag Hosting Integration: - Add edge compute module for serverless functions - Support edge functions (WASM, JS, Python runtimes) - Enable server-side rendering and image optimization - Add AI/ML inference at the edge - Feature-gated behind 'compute' flag Docker Deployment: - Add docker-compose.compute.yml for compute layer - Deploy orchestrator, CPU workers, WASM worker, spot market - Include Redis for task queue and Prometheus for metrics - Reserved ports: 17250-17290 for compute services
385 lines
9.9 KiB
Rust
385 lines
9.9 KiB
Rust
//! Compute integration for edge functions and SSR.
|
|
//!
|
|
//! Enables serverless compute capabilities for hosted websites:
|
|
//! - Edge functions (API routes, middleware)
|
|
//! - Server-side rendering (SSR)
|
|
//! - Image optimization
|
|
//! - AI/ML inference at the edge
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::time::Duration;
|
|
|
|
/// Edge function configuration.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct EdgeFunctionConfig {
|
|
/// Function name/path.
|
|
pub name: String,
|
|
/// Runtime (wasm, js).
|
|
pub runtime: EdgeRuntime,
|
|
/// Memory limit in MB.
|
|
pub memory_mb: u32,
|
|
/// Timeout in seconds.
|
|
pub timeout_secs: u32,
|
|
/// Environment variables.
|
|
pub env: HashMap<String, String>,
|
|
/// Region preferences.
|
|
pub regions: Vec<String>,
|
|
}
|
|
|
|
impl Default for EdgeFunctionConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
name: String::new(),
|
|
runtime: EdgeRuntime::Wasm,
|
|
memory_mb: 128,
|
|
timeout_secs: 10,
|
|
env: HashMap::new(),
|
|
regions: vec!["global".to_string()],
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Supported edge runtimes.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum EdgeRuntime {
|
|
/// WebAssembly (via synor-compute WASM workers).
|
|
Wasm,
|
|
/// JavaScript (V8 isolates).
|
|
JavaScript,
|
|
/// Python (via WebAssembly).
|
|
Python,
|
|
}
|
|
|
|
/// Edge function request.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct EdgeRequest {
|
|
/// HTTP method.
|
|
pub method: String,
|
|
/// Request path.
|
|
pub path: String,
|
|
/// Query parameters.
|
|
pub query: HashMap<String, String>,
|
|
/// Headers.
|
|
pub headers: HashMap<String, String>,
|
|
/// Request body.
|
|
pub body: Option<Vec<u8>>,
|
|
/// Client IP.
|
|
pub client_ip: Option<String>,
|
|
/// Geolocation.
|
|
pub geo: Option<GeoInfo>,
|
|
}
|
|
|
|
/// Geolocation information.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct GeoInfo {
|
|
/// Country code.
|
|
pub country: String,
|
|
/// Region/state.
|
|
pub region: Option<String>,
|
|
/// City.
|
|
pub city: Option<String>,
|
|
/// Latitude.
|
|
pub latitude: Option<f64>,
|
|
/// Longitude.
|
|
pub longitude: Option<f64>,
|
|
}
|
|
|
|
/// Edge function response.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct EdgeResponse {
|
|
/// HTTP status code.
|
|
pub status: u16,
|
|
/// Response headers.
|
|
pub headers: HashMap<String, String>,
|
|
/// Response body.
|
|
pub body: Vec<u8>,
|
|
}
|
|
|
|
impl EdgeResponse {
|
|
/// Create a 200 OK response.
|
|
pub fn ok(body: impl Into<Vec<u8>>) -> Self {
|
|
Self {
|
|
status: 200,
|
|
headers: HashMap::new(),
|
|
body: body.into(),
|
|
}
|
|
}
|
|
|
|
/// Create a JSON response.
|
|
pub fn json<T: Serialize>(data: &T) -> Result<Self, serde_json::Error> {
|
|
let body = serde_json::to_vec(data)?;
|
|
let mut headers = HashMap::new();
|
|
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
Ok(Self {
|
|
status: 200,
|
|
headers,
|
|
body,
|
|
})
|
|
}
|
|
|
|
/// Create a redirect response.
|
|
pub fn redirect(location: &str, permanent: bool) -> Self {
|
|
let mut headers = HashMap::new();
|
|
headers.insert("Location".to_string(), location.to_string());
|
|
Self {
|
|
status: if permanent { 301 } else { 302 },
|
|
headers,
|
|
body: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Create an error response.
|
|
pub fn error(status: u16, message: &str) -> Self {
|
|
Self {
|
|
status,
|
|
headers: HashMap::new(),
|
|
body: message.as_bytes().to_vec(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Edge compute executor.
|
|
#[derive(Clone, Debug)]
|
|
pub struct EdgeCompute {
|
|
/// Compute endpoint URL.
|
|
endpoint: String,
|
|
/// Default timeout.
|
|
default_timeout: Duration,
|
|
/// Enabled flag.
|
|
enabled: bool,
|
|
}
|
|
|
|
impl EdgeCompute {
|
|
/// Create a new edge compute executor.
|
|
pub fn new(endpoint: &str) -> Self {
|
|
Self {
|
|
endpoint: endpoint.to_string(),
|
|
default_timeout: Duration::from_secs(10),
|
|
enabled: true,
|
|
}
|
|
}
|
|
|
|
/// Create a disabled edge compute (local execution only).
|
|
pub fn disabled() -> Self {
|
|
Self {
|
|
endpoint: String::new(),
|
|
default_timeout: Duration::from_secs(10),
|
|
enabled: false,
|
|
}
|
|
}
|
|
|
|
/// Check if edge compute is enabled.
|
|
pub fn is_enabled(&self) -> bool {
|
|
self.enabled
|
|
}
|
|
|
|
/// Execute an edge function.
|
|
pub async fn execute(
|
|
&self,
|
|
config: &EdgeFunctionConfig,
|
|
request: EdgeRequest,
|
|
) -> Result<EdgeResponse, EdgeError> {
|
|
if !self.enabled {
|
|
return Err(EdgeError::NotEnabled);
|
|
}
|
|
|
|
// In a full implementation, this would:
|
|
// 1. Route to the nearest compute node
|
|
// 2. Load the function bytecode from storage
|
|
// 3. Execute in a sandboxed environment
|
|
// 4. Return the response
|
|
|
|
// For now, return a stub response
|
|
Ok(EdgeResponse::ok(format!(
|
|
"Edge function '{}' executed",
|
|
config.name
|
|
)))
|
|
}
|
|
|
|
/// Execute image optimization.
|
|
pub async fn optimize_image(
|
|
&self,
|
|
image_data: &[u8],
|
|
options: ImageOptimizeOptions,
|
|
) -> Result<Vec<u8>, EdgeError> {
|
|
if !self.enabled {
|
|
return Err(EdgeError::NotEnabled);
|
|
}
|
|
|
|
// In a full implementation, this would use compute nodes for:
|
|
// - Image resizing
|
|
// - Format conversion (WebP, AVIF)
|
|
// - Quality optimization
|
|
// - Face/object detection for smart cropping
|
|
|
|
Ok(image_data.to_vec()) // Stub: return original
|
|
}
|
|
|
|
/// Run AI inference at the edge.
|
|
pub async fn inference(
|
|
&self,
|
|
model: &str,
|
|
input: &[u8],
|
|
) -> Result<Vec<u8>, EdgeError> {
|
|
if !self.enabled {
|
|
return Err(EdgeError::NotEnabled);
|
|
}
|
|
|
|
// In a full implementation, this would:
|
|
// 1. Route to a compute node with the model loaded
|
|
// 2. Use NPU/GPU for inference
|
|
// 3. Return the results
|
|
|
|
Ok(Vec::new()) // Stub
|
|
}
|
|
}
|
|
|
|
impl Default for EdgeCompute {
|
|
fn default() -> Self {
|
|
Self::disabled()
|
|
}
|
|
}
|
|
|
|
/// Image optimization options.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct ImageOptimizeOptions {
|
|
/// Target width.
|
|
pub width: Option<u32>,
|
|
/// Target height.
|
|
pub height: Option<u32>,
|
|
/// Output format.
|
|
pub format: ImageFormat,
|
|
/// Quality (0-100).
|
|
pub quality: u8,
|
|
/// Fit mode.
|
|
pub fit: ImageFit,
|
|
}
|
|
|
|
impl Default for ImageOptimizeOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
width: None,
|
|
height: None,
|
|
format: ImageFormat::Auto,
|
|
quality: 80,
|
|
fit: ImageFit::Cover,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Image output formats.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum ImageFormat {
|
|
/// Auto-detect best format.
|
|
Auto,
|
|
/// WebP.
|
|
WebP,
|
|
/// AVIF.
|
|
Avif,
|
|
/// JPEG.
|
|
Jpeg,
|
|
/// PNG.
|
|
Png,
|
|
}
|
|
|
|
/// Image fit modes.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum ImageFit {
|
|
/// Cover the area (crop if needed).
|
|
Cover,
|
|
/// Contain within area (letterbox if needed).
|
|
Contain,
|
|
/// Fill the area (stretch if needed).
|
|
Fill,
|
|
/// No resizing, just format conversion.
|
|
None,
|
|
}
|
|
|
|
/// Edge compute errors.
|
|
#[derive(Clone, Debug)]
|
|
pub enum EdgeError {
|
|
/// Edge compute not enabled.
|
|
NotEnabled,
|
|
/// Timeout.
|
|
Timeout,
|
|
/// Function not found.
|
|
FunctionNotFound(String),
|
|
/// Runtime error.
|
|
RuntimeError(String),
|
|
/// Out of memory.
|
|
OutOfMemory,
|
|
/// Invalid input.
|
|
InvalidInput(String),
|
|
}
|
|
|
|
impl std::fmt::Display for EdgeError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
EdgeError::NotEnabled => write!(f, "Edge compute not enabled"),
|
|
EdgeError::Timeout => write!(f, "Function execution timed out"),
|
|
EdgeError::FunctionNotFound(name) => write!(f, "Function not found: {}", name),
|
|
EdgeError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
|
|
EdgeError::OutOfMemory => write!(f, "Out of memory"),
|
|
EdgeError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for EdgeError {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_edge_response() {
|
|
let resp = EdgeResponse::ok(b"Hello, World!".to_vec());
|
|
assert_eq!(resp.status, 200);
|
|
assert_eq!(resp.body, b"Hello, World!");
|
|
|
|
let redirect = EdgeResponse::redirect("/new-path", false);
|
|
assert_eq!(redirect.status, 302);
|
|
assert_eq!(
|
|
redirect.headers.get("Location"),
|
|
Some(&"/new-path".to_string())
|
|
);
|
|
|
|
let error = EdgeResponse::error(404, "Not Found");
|
|
assert_eq!(error.status, 404);
|
|
}
|
|
|
|
#[test]
|
|
fn test_edge_function_config() {
|
|
let config = EdgeFunctionConfig {
|
|
name: "api/hello".to_string(),
|
|
runtime: EdgeRuntime::Wasm,
|
|
memory_mb: 256,
|
|
timeout_secs: 30,
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(config.name, "api/hello");
|
|
assert_eq!(config.memory_mb, 256);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_compute_disabled() {
|
|
let compute = EdgeCompute::disabled();
|
|
assert!(!compute.is_enabled());
|
|
|
|
let config = EdgeFunctionConfig::default();
|
|
let request = EdgeRequest {
|
|
method: "GET".to_string(),
|
|
path: "/api/test".to_string(),
|
|
query: HashMap::new(),
|
|
headers: HashMap::new(),
|
|
body: None,
|
|
client_ip: None,
|
|
geo: None,
|
|
};
|
|
|
|
let result = compute.execute(&config, request).await;
|
|
assert!(matches!(result, Err(EdgeError::NotEnabled)));
|
|
}
|
|
}
|