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