microsandbox_server/
handler.rs

1//! Request handlers for the microsandbox server.
2//!
3//! This module implements:
4//! - API endpoint handlers
5//! - Request processing logic
6//! - Response formatting
7//!
8//! The module provides:
9//! - Handler functions for API routes
10//! - Request validation and processing
11//! - Response generation and error handling
12
13use axum::{
14    body::Body,
15    debug_handler,
16    extract::{Path, State},
17    http::{Request, StatusCode},
18    response::{IntoResponse, Response},
19    Json,
20};
21use microsandbox_core::management::{menv, orchestra};
22use microsandbox_utils::{DEFAULT_CONFIG, DEFAULT_PORTAL_GUEST_PORT, MICROSANDBOX_CONFIG_FILENAME};
23use reqwest;
24use serde_json::{self, json};
25use serde_yaml;
26use std::path::PathBuf;
27use tokio::{
28    fs as tokio_fs,
29    time::{sleep, timeout, Duration},
30};
31use tracing::{debug, trace, warn};
32
33use crate::{
34    error::ServerError,
35    mcp, middleware,
36    payload::{
37        JsonRpcError, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseOrNotification,
38        RegularMessageResponse, SandboxMetricsGetParams, SandboxStartParams, SandboxStopParams,
39        JSONRPC_VERSION,
40    },
41    state::AppState,
42    SandboxStatus, SandboxStatusResponse, ServerResult,
43};
44
45//--------------------------------------------------------------------------------------------------
46// Functions: REST API Handlers
47//--------------------------------------------------------------------------------------------------
48
49/// Handler for health check
50pub async fn health() -> ServerResult<impl IntoResponse> {
51    Ok((
52        StatusCode::OK,
53        Json(RegularMessageResponse {
54            message: "Service is healthy".to_string(),
55        }),
56    ))
57}
58
59//--------------------------------------------------------------------------------------------------
60// Functions: JSON-RPC Handlers
61//--------------------------------------------------------------------------------------------------
62
63/// Dedicated MCP handler for Model Context Protocol requests
64#[debug_handler]
65pub async fn mcp_handler(
66    State(state): State<AppState>,
67    Json(request): Json<JsonRpcRequest>,
68) -> ServerResult<impl IntoResponse> {
69    debug!(?request, "Received MCP request");
70    // Check for required JSON-RPC fields
71    if request.jsonrpc != JSONRPC_VERSION {
72        let error = JsonRpcError {
73            code: -32600,
74            message: "Invalid or missing jsonrpc version field".to_string(),
75            data: None,
76        };
77        return Ok(JsonRpcResponseOrNotification::error(
78            error,
79            request.id.clone(),
80        ));
81    }
82
83    // Extract the ID before moving the request
84    let request_id = request.id.clone();
85
86    // Handle MCP methods directly since all requests to /mcp are MCP requests
87    match mcp::handle_mcp_method(state, request).await {
88        Ok(response) => {
89            // The enum handles both regular responses and notifications
90            Ok(response)
91        }
92        Err(e) => {
93            let error = JsonRpcError {
94                code: -32603,
95                message: format!("MCP method error: {}", e),
96                data: None,
97            };
98            Ok(JsonRpcResponseOrNotification::error(error, request_id))
99        }
100    }
101}
102
103/// Main JSON-RPC handler that dispatches to the appropriate method
104#[debug_handler]
105pub async fn json_rpc_handler(
106    State(state): State<AppState>,
107    Json(request): Json<JsonRpcRequest>,
108) -> ServerResult<impl IntoResponse> {
109    debug!(?request, "Received JSON-RPC request");
110
111    // Check for required JSON-RPC fields
112    if request.jsonrpc != JSONRPC_VERSION {
113        let error = JsonRpcError {
114            code: -32600,
115            message: "Invalid or missing jsonrpc version field".to_string(),
116            data: None,
117        };
118        return Ok((
119            StatusCode::BAD_REQUEST,
120            Json(JsonRpcResponse::error(error, request.id.clone())),
121        ));
122    }
123
124    let method = request.method.as_str();
125    let id = request.id.clone();
126
127    match method {
128        // Server specific methods
129        "sandbox.start" => {
130            // Parse the params into a SandboxStartRequest
131            let start_params: SandboxStartParams =
132                serde_json::from_value(request.params.clone()).map_err(|e| {
133                    ServerError::ValidationError(crate::error::ValidationError::InvalidInput(
134                        format!("Invalid params for sandbox.start: {}", e),
135                    ))
136                })?;
137
138            // Call the sandbox_up_impl function
139            let result = sandbox_start_impl(state, start_params).await?;
140
141            // Create JSON-RPC response with success
142            Ok((
143                StatusCode::OK,
144                Json(JsonRpcResponse::success(json!(result), id)),
145            ))
146        }
147        "sandbox.stop" => {
148            // Parse the params into a SandboxStopRequest
149            let stop_params: SandboxStopParams = serde_json::from_value(request.params.clone())
150                .map_err(|e| {
151                    ServerError::ValidationError(crate::error::ValidationError::InvalidInput(
152                        format!("Invalid params for sandbox.stop: {}", e),
153                    ))
154                })?;
155
156            // Call the sandbox_down_impl function
157            let result = sandbox_stop_impl(state, stop_params).await?;
158
159            // Create JSON-RPC response with success
160            Ok((
161                StatusCode::OK,
162                Json(JsonRpcResponse::success(json!(result), id)),
163            ))
164        }
165        "sandbox.metrics.get" => {
166            // Parse the params into a SandboxMetricsGetRequest
167            let metrics_params: SandboxMetricsGetParams =
168                serde_json::from_value(request.params.clone()).map_err(|e| {
169                    ServerError::ValidationError(crate::error::ValidationError::InvalidInput(
170                        format!("Invalid params for sandbox.metrics.get: {}", e),
171                    ))
172                })?;
173
174            // Call the sandbox_get_metrics_impl function with state and request
175            let result = sandbox_get_metrics_impl(state.clone(), metrics_params).await?;
176
177            // Create JSON-RPC response with success
178            Ok((
179                StatusCode::OK,
180                Json(JsonRpcResponse::success(json!(result), id)),
181            ))
182        }
183
184        // Portal-forwarded methods
185        "sandbox.repl.run" | "sandbox.command.run" => {
186            // Forward these RPC methods to the portal
187            match forward_rpc_to_portal(state, request).await {
188                Ok((status, json_response)) => Ok((status, json_response)),
189                Err(e) => Err(e),
190            }
191        }
192
193        _ => {
194            let error = JsonRpcError {
195                code: -32601,
196                message: format!("Method not found: {}", method),
197                data: None,
198            };
199            Ok((
200                StatusCode::NOT_FOUND,
201                Json(JsonRpcResponse::error(error, id)),
202            ))
203        }
204    }
205}
206
207/// Forwards the JSON-RPC request to the portal service
208pub async fn forward_rpc_to_portal(
209    state: AppState,
210    request: JsonRpcRequest,
211) -> ServerResult<(StatusCode, Json<JsonRpcResponse>)> {
212    // Extract sandbox information from request context or method parameters
213    // The method will have the format "sandbox.repl.run" etc.
214    // The method params will have a sandbox_name and namespace parameter
215
216    // Extract the sandbox and namespace from the parameters
217    let (sandbox_name, namespace) = if let Some(params) = request.params.as_object() {
218        // Get sandbox name
219        let sandbox = params
220            .get("sandbox")
221            .and_then(|v| v.as_str())
222            .ok_or_else(|| {
223                ServerError::ValidationError(crate::error::ValidationError::InvalidInput(
224                    "Missing required 'sandbox' parameter for portal request".to_string(),
225                ))
226            })?;
227
228        // Get namespace
229        let namespace = params
230            .get("namespace")
231            .and_then(|v| v.as_str())
232            .ok_or_else(|| {
233                ServerError::ValidationError(crate::error::ValidationError::InvalidInput(
234                    "Missing required 'namespace' parameter for portal request".to_string(),
235                ))
236            })?;
237
238        (sandbox, namespace)
239    } else {
240        return Err(ServerError::ValidationError(
241            crate::error::ValidationError::InvalidInput(
242                "Request parameters must be an object containing 'sandbox' and 'namespace'"
243                    .to_string(),
244            ),
245        ));
246    };
247
248    // Get the portal URL specifically for this sandbox
249    let portal_url = state
250        .get_portal_url_for_sandbox(namespace, sandbox_name)
251        .await?;
252
253    // Create a full URL to the portal's JSON-RPC endpoint
254    let portal_rpc_url = format!("{}/api/v1/rpc", portal_url);
255
256    debug!("Forwarding RPC to portal: {}", portal_rpc_url);
257
258    // Create an HTTP client
259    let client = reqwest::Client::new();
260
261    // Configure connection retry parameters
262    const MAX_RETRIES: u32 = 10_000;
263    const TIMEOUT_MS: u64 = 50;
264
265    // Try to establish a connection to the portal before sending the actual request
266    let mut retry_count = 0;
267    let mut last_error = None;
268
269    // Keep trying to connect until we succeed or hit max retries
270    while retry_count < MAX_RETRIES {
271        // Check if portal is available with a HEAD request
272        match client
273            .head(&portal_url)
274            .timeout(Duration::from_millis(TIMEOUT_MS))
275            .send()
276            .await
277        {
278            Ok(response) => {
279                // Any HTTP response (success or error) means we successfully connected
280                debug!(
281                    "Successfully connected to portal after {} retries (status: {})",
282                    retry_count,
283                    response.status()
284                );
285                break;
286            }
287            Err(e) => {
288                // Track the error for potential reporting but keep retrying
289                last_error = Some(e);
290                trace!("Connection attempt {} failed, retrying...", retry_count + 1);
291            }
292        }
293
294        // Increment retry counter
295        retry_count += 1;
296    }
297
298    // If we've hit the max retries and still can't connect, report the error
299    if retry_count >= MAX_RETRIES {
300        let error_msg = if let Some(e) = last_error {
301            format!(
302                "Failed to connect to portal after {} retries: {}",
303                MAX_RETRIES, e
304            )
305        } else {
306            format!("Failed to connect to portal after {} retries", MAX_RETRIES)
307        };
308        return Err(ServerError::InternalError(error_msg));
309    }
310
311    // Forward the request to the portal now that we've verified connectivity
312    let response = client
313        .post(&portal_rpc_url)
314        .json(&request)
315        .send()
316        .await
317        .map_err(|e| {
318            ServerError::InternalError(format!("Failed to forward RPC to portal: {}", e))
319        })?;
320
321    // Check if the request was successful
322    if !response.status().is_success() {
323        let status = response.status();
324        let error_text = response
325            .text()
326            .await
327            .unwrap_or_else(|_| "Unknown error".to_string());
328
329        return Err(ServerError::InternalError(format!(
330            "Portal returned error status {}: {}",
331            status, error_text
332        )));
333    }
334
335    // Parse the JSON-RPC response from the portal
336    let portal_response: JsonRpcResponse = response.json().await.map_err(|e| {
337        ServerError::InternalError(format!("Failed to parse portal response: {}", e))
338    })?;
339
340    // Return the portal's response directly
341    Ok((StatusCode::OK, Json(portal_response)))
342}
343
344/// Implementation for starting a sandbox
345pub async fn sandbox_start_impl(
346    state: AppState,
347    params: SandboxStartParams,
348) -> ServerResult<String> {
349    // Validate sandbox name and namespace
350    validate_sandbox_name(&params.sandbox)?;
351    validate_namespace(&params.namespace)?;
352
353    let namespace_dir = state
354        .get_config()
355        .get_namespace_dir()
356        .join(&params.namespace);
357    let config_file = MICROSANDBOX_CONFIG_FILENAME;
358    let config_path = namespace_dir.join(config_file);
359    let sandbox = &params.sandbox;
360
361    // Create namespace directory if it doesn't exist
362    if !namespace_dir.exists() {
363        tokio_fs::create_dir_all(&namespace_dir)
364            .await
365            .map_err(|e| {
366                ServerError::InternalError(format!("Failed to create namespace directory: {}", e))
367            })?;
368
369        // Initialize microsandbox environment
370        menv::initialize(Some(namespace_dir.clone()))
371            .await
372            .map_err(|e| {
373                ServerError::InternalError(format!(
374                    "Failed to initialize microsandbox environment: {}",
375                    e
376                ))
377            })?;
378    }
379
380    // Check if we have a valid configuration to proceed with
381    let has_config_in_request = params
382        .config
383        .as_ref()
384        .and_then(|c| c.image.as_ref())
385        .is_some();
386    let has_existing_config_file = config_path.exists();
387
388    if !has_config_in_request && !has_existing_config_file {
389        return Err(ServerError::ValidationError(
390            crate::error::ValidationError::InvalidInput(format!(
391                "No configuration provided and no existing configuration found for sandbox '{}'",
392                sandbox
393            )),
394        ));
395    }
396
397    // Load or create the config
398    let mut config_yaml: serde_yaml::Value;
399
400    // Read or initialize the configuration
401    if has_existing_config_file {
402        // Read the existing config
403        let config_content = tokio_fs::read_to_string(&config_path).await.map_err(|e| {
404            ServerError::InternalError(format!("Failed to read config file: {}", e))
405        })?;
406
407        // Parse the config as YAML
408        config_yaml = serde_yaml::from_str(&config_content).map_err(|e| {
409            ServerError::InternalError(format!("Failed to parse config file: {}", e))
410        })?;
411
412        // If we're relying on existing config, verify that the sandbox exists in it
413        if !has_config_in_request {
414            let has_sandbox_config = config_yaml
415                .get("sandboxes")
416                .and_then(|sandboxes| sandboxes.get(sandbox))
417                .is_some();
418
419            if !has_sandbox_config {
420                return Err(ServerError::ValidationError(
421                    crate::error::ValidationError::InvalidInput(format!(
422                        "Sandbox '{}' not found in existing configuration",
423                        sandbox
424                    )),
425                ));
426            }
427        }
428    } else {
429        // Create a new config with default values
430        if !has_config_in_request {
431            return Err(ServerError::ValidationError(
432                crate::error::ValidationError::InvalidInput(
433                    "No configuration provided and no existing configuration file".to_string(),
434                ),
435            ));
436        }
437
438        // Create default config
439        tokio_fs::write(&config_path, DEFAULT_CONFIG)
440            .await
441            .map_err(|e| {
442                ServerError::InternalError(format!("Failed to create config file: {}", e))
443            })?;
444
445        // Parse default config
446        config_yaml = serde_yaml::from_str(DEFAULT_CONFIG).map_err(|e| {
447            ServerError::InternalError(format!("Failed to parse default config: {}", e))
448        })?;
449    }
450
451    // Ensure sandboxes field exists
452    if !config_yaml.is_mapping() {
453        config_yaml = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
454    }
455
456    let config_map = config_yaml.as_mapping_mut().unwrap();
457    if !config_map.contains_key(&serde_yaml::Value::String("sandboxes".to_string())) {
458        config_map.insert(
459            serde_yaml::Value::String("sandboxes".to_string()),
460            serde_yaml::Value::Mapping(serde_yaml::Mapping::new()),
461        );
462    }
463
464    // Get the sandboxes mapping
465    let sandboxes_key = serde_yaml::Value::String("sandboxes".to_string());
466    let sandboxes_value = config_map.get_mut(&sandboxes_key).unwrap();
467
468    // Check if sandboxes value is a mapping, if not, replace it with an empty mapping
469    if !sandboxes_value.is_mapping() {
470        *sandboxes_value = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
471    }
472
473    let sandboxes_map = sandboxes_value.as_mapping_mut().unwrap();
474
475    // If config is provided and we have an image, update the sandbox configuration
476    if let Some(config) = &params.config {
477        if config.image.is_some() {
478            // Create or update sandbox entry
479            let mut sandbox_map = serde_yaml::Mapping::new();
480
481            // Set required image field
482            if let Some(image) = &config.image {
483                sandbox_map.insert(
484                    serde_yaml::Value::String("image".to_string()),
485                    serde_yaml::Value::String(image.clone()),
486                );
487            }
488
489            // Set optional fields
490            if let Some(memory) = config.memory {
491                sandbox_map.insert(
492                    serde_yaml::Value::String("memory".to_string()),
493                    serde_yaml::Value::Number(serde_yaml::Number::from(memory)),
494                );
495            }
496
497            if let Some(cpus) = config.cpus {
498                sandbox_map.insert(
499                    serde_yaml::Value::String("cpus".to_string()),
500                    serde_yaml::Value::Number(serde_yaml::Number::from(cpus)),
501                );
502            }
503
504            if !config.volumes.is_empty() {
505                let volumes_array = config
506                    .volumes
507                    .iter()
508                    .map(|v| serde_yaml::Value::String(v.clone()))
509                    .collect::<Vec<_>>();
510                sandbox_map.insert(
511                    serde_yaml::Value::String("volumes".to_string()),
512                    serde_yaml::Value::Sequence(volumes_array),
513                );
514            }
515
516            if !config.ports.is_empty() {
517                let ports_array = config
518                    .ports
519                    .iter()
520                    .map(|p| serde_yaml::Value::String(p.clone()))
521                    .collect::<Vec<_>>();
522                sandbox_map.insert(
523                    serde_yaml::Value::String("ports".to_string()),
524                    serde_yaml::Value::Sequence(ports_array),
525                );
526            }
527
528            if !config.envs.is_empty() {
529                let envs_array = config
530                    .envs
531                    .iter()
532                    .map(|e| serde_yaml::Value::String(e.clone()))
533                    .collect::<Vec<_>>();
534                sandbox_map.insert(
535                    serde_yaml::Value::String("envs".to_string()),
536                    serde_yaml::Value::Sequence(envs_array),
537                );
538            }
539
540            if !config.depends_on.is_empty() {
541                let depends_on_array = config
542                    .depends_on
543                    .iter()
544                    .map(|d| serde_yaml::Value::String(d.clone()))
545                    .collect::<Vec<_>>();
546                sandbox_map.insert(
547                    serde_yaml::Value::String("depends_on".to_string()),
548                    serde_yaml::Value::Sequence(depends_on_array),
549                );
550            }
551
552            if let Some(workdir) = &config.workdir {
553                sandbox_map.insert(
554                    serde_yaml::Value::String("workdir".to_string()),
555                    serde_yaml::Value::String(workdir.clone()),
556                );
557            }
558
559            if let Some(shell) = &config.shell {
560                sandbox_map.insert(
561                    serde_yaml::Value::String("shell".to_string()),
562                    serde_yaml::Value::String(shell.clone()),
563                );
564            }
565
566            if !config.scripts.is_empty() {
567                let mut scripts_map = serde_yaml::Mapping::new();
568                for (script_name, script) in &config.scripts {
569                    scripts_map.insert(
570                        serde_yaml::Value::String(script_name.clone()),
571                        serde_yaml::Value::String(script.clone()),
572                    );
573                }
574                sandbox_map.insert(
575                    serde_yaml::Value::String("scripts".to_string()),
576                    serde_yaml::Value::Mapping(scripts_map),
577                );
578            }
579
580            if let Some(exec) = &config.exec {
581                sandbox_map.insert(
582                    serde_yaml::Value::String("exec".to_string()),
583                    serde_yaml::Value::String(exec.clone()),
584                );
585            }
586
587            // Replace or add the sandbox in the config
588            sandboxes_map.insert(
589                serde_yaml::Value::String(sandbox.clone()),
590                serde_yaml::Value::Mapping(sandbox_map),
591            );
592        }
593    }
594
595    // Assign a port for this sandbox
596    let sandbox_key = format!("{}/{}", params.namespace, params.sandbox);
597    let port = {
598        let mut port_manager = state.get_port_manager().write().await;
599        port_manager.assign_port(&sandbox_key).await.map_err(|e| {
600            ServerError::InternalError(format!("Failed to assign portal port: {}", e))
601        })?
602    };
603
604    debug!("Assigned portal port {} to sandbox {}", port, sandbox_key);
605
606    // Get the specific sandbox configuration
607    let sandbox_config = sandboxes_map
608        .get_mut(&serde_yaml::Value::String(sandbox.clone()))
609        .ok_or_else(|| {
610            ServerError::InternalError(format!("Sandbox '{}' not found in configuration", sandbox))
611        })?
612        .as_mapping_mut()
613        .ok_or_else(|| {
614            ServerError::InternalError(format!(
615                "Sandbox '{}' configuration is not a mapping",
616                sandbox
617            ))
618        })?;
619
620    // Add or update the portal port mapping
621    let guest_port = DEFAULT_PORTAL_GUEST_PORT;
622    let portal_port_mapping = format!("{}:{}", port, guest_port);
623
624    let ports_key = serde_yaml::Value::String("ports".to_string());
625
626    if let Some(ports) = sandbox_config.get_mut(&ports_key) {
627        if let Some(ports_seq) = ports.as_sequence_mut() {
628            // Filter out any existing portal port mappings
629            ports_seq.retain(|p| {
630                p.as_str()
631                    .map(|s| !s.ends_with(&format!(":{}", guest_port)))
632                    .unwrap_or(true)
633            });
634
635            // Add the new port mapping
636            ports_seq.push(serde_yaml::Value::String(portal_port_mapping));
637        }
638    } else {
639        // Create a new ports list with the portal port mapping
640        let mut ports_seq = serde_yaml::Sequence::new();
641        ports_seq.push(serde_yaml::Value::String(portal_port_mapping));
642        sandbox_config.insert(ports_key, serde_yaml::Value::Sequence(ports_seq));
643    }
644
645    // Write the updated config back to the file
646    let updated_config = serde_yaml::to_string(&config_yaml)
647        .map_err(|e| ServerError::InternalError(format!("Failed to serialize config: {}", e)))?;
648
649    tokio_fs::write(&config_path, updated_config)
650        .await
651        .map_err(|e| ServerError::InternalError(format!("Failed to write config file: {}", e)))?;
652
653    // Start the sandbox
654    orchestra::up(
655        vec![sandbox.clone()],
656        Some(&namespace_dir),
657        Some(config_file),
658        true,
659    )
660    .await
661    .map_err(|e| {
662        ServerError::InternalError(format!("Failed to start sandbox {}: {}", params.sandbox, e))
663    })?;
664
665    // Determine if this is a first-time image pull based on config
666    let potentially_first_time_pull = if let Some(config) = &params.config {
667        config.image.is_some()
668    } else {
669        false
670    };
671
672    // Set appropriate timeout based on whether this might be a first-time image pull
673    // Using longer timeout for first-time pulls to allow for image downloading
674    let poll_timeout = if potentially_first_time_pull {
675        Duration::from_secs(180) // 3 minutes for first-time image pulls
676    } else {
677        Duration::from_secs(60) // 1 minute for regular starts
678    };
679
680    // Wait for the sandbox to actually start running with a timeout
681    debug!("Waiting for sandbox {} to start...", sandbox);
682    match timeout(
683        poll_timeout,
684        poll_sandbox_until_running(&params.sandbox, &namespace_dir, config_file),
685    )
686    .await
687    {
688        Ok(result) => match result {
689            Ok(_) => {
690                debug!("Sandbox {} is now running", sandbox);
691                Ok(format!("Sandbox {} started successfully", params.sandbox))
692            }
693            Err(e) => {
694                // The sandbox was started but polling failed for some reason
695                warn!("Failed to verify sandbox {} is running: {}", sandbox, e);
696                Ok(format!(
697                    "Sandbox {} was started, but couldn't verify it's running: {}",
698                    params.sandbox, e
699                ))
700            }
701        },
702        Err(_) => {
703            // Timeout occurred, but we still return success since the sandbox might still be starting
704            warn!("Timeout waiting for sandbox {} to start", sandbox);
705            Ok(format!(
706                "Sandbox {} was started, but timed out waiting for it to be fully running. It may still be initializing.",
707                params.sandbox
708            ))
709        }
710    }
711}
712
713/// Polls the sandbox until it's verified to be running
714async fn poll_sandbox_until_running(
715    sandbox_name: &str,
716    namespace_dir: &PathBuf,
717    config_file: &str,
718) -> ServerResult<()> {
719    const POLL_INTERVAL: Duration = Duration::from_millis(20);
720    const MAX_ATTEMPTS: usize = 2500; // Increased to maintain similar overall timeout period with faster polling
721
722    for attempt in 1..=MAX_ATTEMPTS {
723        // Check if the sandbox is running
724        let statuses = orchestra::status(
725            vec![sandbox_name.to_string()],
726            Some(namespace_dir),
727            Some(config_file),
728        )
729        .await
730        .map_err(|e| ServerError::InternalError(format!("Failed to get sandbox status: {}", e)))?;
731
732        // Find our sandbox in the results
733        if let Some(status) = statuses.iter().find(|s| s.name == sandbox_name) {
734            if status.running {
735                // Sandbox is running, we're done
736                debug!(
737                    "Sandbox {} is running (verified on attempt {})",
738                    sandbox_name, attempt
739                );
740                return Ok(());
741            }
742        }
743
744        // Sleep before the next attempt
745        sleep(POLL_INTERVAL).await;
746    }
747
748    // If we reach here, we've exceeded our attempt limit
749    Err(ServerError::InternalError(format!(
750        "Exceeded maximum attempts to verify sandbox {} is running",
751        sandbox_name
752    )))
753}
754
755/// Implementation for stopping a sandbox
756pub async fn sandbox_stop_impl(state: AppState, params: SandboxStopParams) -> ServerResult<String> {
757    // Validate sandbox name and namespace
758    validate_sandbox_name(&params.sandbox)?;
759    validate_namespace(&params.namespace)?;
760
761    let namespace_dir = state
762        .get_config()
763        .get_namespace_dir()
764        .join(&params.namespace);
765    let config_file = MICROSANDBOX_CONFIG_FILENAME;
766    let sandbox = &params.sandbox;
767    let sandbox_key = format!("{}/{}", params.namespace, params.sandbox);
768
769    // Verify that the namespace directory exists
770    if !namespace_dir.exists() {
771        return Err(ServerError::ValidationError(
772            crate::error::ValidationError::InvalidInput(format!(
773                "Namespace directory '{}' does not exist",
774                params.namespace
775            )),
776        ));
777    }
778
779    // Verify that the config file exists
780    let config_path = namespace_dir.join(config_file);
781    if !config_path.exists() {
782        return Err(ServerError::ValidationError(
783            crate::error::ValidationError::InvalidInput(format!(
784                "Configuration file not found for namespace '{}'",
785                params.namespace
786            )),
787        ));
788    }
789
790    // Stop the sandbox using orchestra::down
791    orchestra::down(
792        vec![sandbox.clone()],
793        Some(&namespace_dir),
794        Some(config_file),
795    )
796    .await
797    .map_err(|e| {
798        ServerError::InternalError(format!("Failed to stop sandbox {}: {}", params.sandbox, e))
799    })?;
800
801    // Release the assigned port
802    {
803        let mut port_manager = state.get_port_manager().write().await;
804        port_manager.release_port(&sandbox_key).await.map_err(|e| {
805            ServerError::InternalError(format!("Failed to release portal port: {}", e))
806        })?;
807    }
808
809    debug!("Released portal port for sandbox {}", sandbox_key);
810
811    // Return success message
812    Ok(format!("Sandbox {} stopped successfully", params.sandbox))
813}
814
815/// Implementation for sandbox metrics
816pub async fn sandbox_get_metrics_impl(
817    state: AppState,
818    params: SandboxMetricsGetParams,
819) -> ServerResult<SandboxStatusResponse> {
820    // Validate namespace - special handling for '*' wildcard
821    if params.namespace != "*" {
822        validate_namespace(&params.namespace)?;
823    }
824
825    // Validate sandbox name if provided
826    if let Some(sandbox) = &params.sandbox {
827        validate_sandbox_name(sandbox)?;
828    }
829
830    let namespaces_dir = state.get_config().get_namespace_dir();
831
832    // Check if the namespaces directory exists
833    if !namespaces_dir.exists() {
834        return Err(ServerError::InternalError(format!(
835            "Namespaces directory '{}' does not exist",
836            namespaces_dir.display()
837        )));
838    }
839
840    // Get all sandboxes metrics based on the request
841    let mut all_statuses = Vec::new();
842
843    // If namespace is "*", get metrics from all namespaces
844    if params.namespace == "*" {
845        // Read namespaces directory
846        let mut entries = tokio::fs::read_dir(&namespaces_dir).await.map_err(|e| {
847            ServerError::InternalError(format!("Failed to read namespaces directory: {}", e))
848        })?;
849
850        // Process each namespace directory
851        while let Some(entry) = entries.next_entry().await.map_err(|e| {
852            ServerError::InternalError(format!("Failed to read namespace directory entry: {}", e))
853        })? {
854            let path = entry.path();
855            if !path.is_dir() {
856                continue;
857            }
858
859            let namespace = path
860                .file_name()
861                .and_then(|n| n.to_str())
862                .unwrap_or("unknown")
863                .to_string();
864
865            // Get metrics for this namespace, filtered by sandbox name if provided
866            let sandbox_names = if let Some(sandbox) = &params.sandbox {
867                vec![sandbox.clone()]
868            } else {
869                vec![]
870            };
871
872            match orchestra::status(sandbox_names, Some(&path), None).await {
873                Ok(statuses) => {
874                    for status in statuses {
875                        // Convert from orchestra::SandboxStatus to our SandboxStatus
876                        all_statuses.push(SandboxStatus {
877                            namespace: namespace.clone(),
878                            name: status.name,
879                            running: status.running,
880                            cpu_usage: status.cpu_usage,
881                            memory_usage: status.memory_usage,
882                            disk_usage: status.disk_usage,
883                        });
884                    }
885                }
886                Err(e) => {
887                    // Log the error but continue with other namespaces
888                    tracing::warn!("Error getting metrics for namespace {}: {}", namespace, e);
889                }
890            }
891        }
892    } else {
893        // Get metrics for a specific namespace
894        let namespace_dir = namespaces_dir.join(&params.namespace);
895
896        // Check if the namespace directory exists
897        if !namespace_dir.exists() {
898            return Err(ServerError::ValidationError(
899                crate::error::ValidationError::InvalidInput(format!(
900                    "Namespace directory '{}' does not exist",
901                    params.namespace
902                )),
903            ));
904        }
905
906        // Get metrics for this namespace, filtered by sandbox name if provided
907        let sandbox_names = if let Some(sandbox) = &params.sandbox {
908            vec![sandbox.clone()]
909        } else {
910            vec![]
911        };
912
913        match orchestra::status(sandbox_names, Some(&namespace_dir), None).await {
914            Ok(statuses) => {
915                for status in statuses {
916                    // Convert from orchestra::SandboxStatus to our SandboxStatus
917                    all_statuses.push(SandboxStatus {
918                        namespace: params.namespace.clone(),
919                        name: status.name,
920                        running: status.running,
921                        cpu_usage: status.cpu_usage,
922                        memory_usage: status.memory_usage,
923                        disk_usage: status.disk_usage,
924                    });
925                }
926            }
927            Err(e) => {
928                return Err(ServerError::InternalError(format!(
929                    "Error getting metrics for namespace {}: {}",
930                    params.namespace, e
931                )));
932            }
933        }
934    }
935
936    Ok(SandboxStatusResponse {
937        sandboxes: all_statuses,
938    })
939}
940
941//--------------------------------------------------------------------------------------------------
942// Functions: Proxy Handlers
943//--------------------------------------------------------------------------------------------------
944
945/// Handler for proxy requests
946pub async fn proxy_request(
947    State(_state): State<AppState>,
948    Path((namespace, sandbox, path)): Path<(String, String, PathBuf)>,
949    req: Request<Body>,
950) -> ServerResult<impl IntoResponse> {
951    // In a real implementation, this would use the middleware::proxy_uri function
952    // to determine the target URI and then forward the request
953
954    let path_str = path.display().to_string();
955
956    // Calculate target URI using our middleware function
957    let original_uri = req.uri().clone();
958    let _target_uri = middleware::proxy_uri(original_uri, &namespace, &sandbox);
959
960    // In a production system, this handler would forward the request to the target URI
961    // For now, we'll just return information about what would be proxied
962
963    let response = format!(
964        "Axum Proxy Request\n\nNamespace: {}\nSandbox: {}\nPath: {}\nMethod: {}\nHeaders: {:?}",
965        namespace,
966        sandbox,
967        path_str,
968        req.method(),
969        req.headers()
970    );
971
972    let result = Response::builder()
973        .status(StatusCode::OK)
974        .header("Content-Type", "text/plain")
975        .body(Body::from(response))
976        .unwrap();
977
978    Ok(result)
979}
980
981/// Fallback handler for proxy requests
982pub async fn proxy_fallback() -> ServerResult<impl IntoResponse> {
983    Ok((StatusCode::NOT_FOUND, "Resource not found"))
984}
985
986//--------------------------------------------------------------------------------------------------
987// Functions: Helpers
988//--------------------------------------------------------------------------------------------------
989
990/// Validates a sandbox name
991fn validate_sandbox_name(name: &str) -> ServerResult<()> {
992    // Check name length
993    if name.is_empty() {
994        return Err(ServerError::ValidationError(
995            crate::error::ValidationError::InvalidInput("Sandbox name cannot be empty".to_string()),
996        ));
997    }
998
999    if name.len() > 63 {
1000        return Err(ServerError::ValidationError(
1001            crate::error::ValidationError::InvalidInput(
1002                "Sandbox name cannot exceed 63 characters".to_string(),
1003            ),
1004        ));
1005    }
1006
1007    // Check name characters
1008    let valid_chars = name
1009        .chars()
1010        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_');
1011
1012    if !valid_chars {
1013        return Err(ServerError::ValidationError(
1014            crate::error::ValidationError::InvalidInput(
1015                "Sandbox name can only contain alphanumeric characters, hyphens, or underscores"
1016                    .to_string(),
1017            ),
1018        ));
1019    }
1020
1021    // Must start with an alphanumeric character
1022    if !name.chars().next().unwrap().is_ascii_alphanumeric() {
1023        return Err(ServerError::ValidationError(
1024            crate::error::ValidationError::InvalidInput(
1025                "Sandbox name must start with an alphanumeric character".to_string(),
1026            ),
1027        ));
1028    }
1029
1030    Ok(())
1031}
1032
1033/// Validates a namespace
1034fn validate_namespace(namespace: &str) -> ServerResult<()> {
1035    // Check namespace length
1036    if namespace.is_empty() {
1037        return Err(ServerError::ValidationError(
1038            crate::error::ValidationError::InvalidInput("Namespace cannot be empty".to_string()),
1039        ));
1040    }
1041
1042    if namespace.len() > 63 {
1043        return Err(ServerError::ValidationError(
1044            crate::error::ValidationError::InvalidInput(
1045                "Namespace cannot exceed 63 characters".to_string(),
1046            ),
1047        ));
1048    }
1049
1050    // Check for wildcard namespace - only valid for queries, not for creation
1051    if namespace == "*" {
1052        return Err(ServerError::ValidationError(
1053            crate::error::ValidationError::InvalidInput(
1054                "Wildcard namespace (*) is not valid for sandbox creation".to_string(),
1055            ),
1056        ));
1057    }
1058
1059    // Check namespace characters
1060    let valid_chars = namespace
1061        .chars()
1062        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_');
1063
1064    if !valid_chars {
1065        return Err(ServerError::ValidationError(
1066            crate::error::ValidationError::InvalidInput(
1067                "Namespace can only contain alphanumeric characters, hyphens, or underscores"
1068                    .to_string(),
1069            ),
1070        ));
1071    }
1072
1073    // Must start with an alphanumeric character
1074    if !namespace.chars().next().unwrap().is_ascii_alphanumeric() {
1075        return Err(ServerError::ValidationError(
1076            crate::error::ValidationError::InvalidInput(
1077                "Namespace must start with an alphanumeric character".to_string(),
1078            ),
1079        ));
1080    }
1081
1082    Ok(())
1083}