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