488 lines
13 KiB
Markdown
488 lines
13 KiB
Markdown
# Monaco Backend - Code Execution Service
|
||
|
||
## Table of Contents
|
||
|
||
1. Introduction
|
||
2. Architecture
|
||
3. Installation
|
||
4. API Reference
|
||
5. Code Execution
|
||
6. Job Queue System
|
||
7. Language Support
|
||
8. Security Considerations
|
||
9. Configuration
|
||
10. Testing
|
||
11. Performance Tuning
|
||
12. Troubleshooting
|
||
|
||
## Introduction
|
||
|
||
Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring.
|
||
|
||
**Key Features:**
|
||
- Multi-language support (Python, Java, C, C++)
|
||
- Secure containerized execution using Docker
|
||
- Resource limiting to prevent abuse
|
||
- Job queuing for managing concurrent executions
|
||
- Detailed execution statistics and monitoring
|
||
- Support for user input via stdin
|
||
- CORS support for browser-based clients
|
||
|
||
## Architecture
|
||
|
||
### Component Overview
|
||
|
||
Monaco follows a layered architecture with the following key components:
|
||
|
||
1. **HTTP Handlers** (handler package) - Processes incoming HTTP requests
|
||
2. **Execution Service** (service package) - Manages code execution in containers
|
||
3. **Job Queue** (queue package) - Controls concurrent execution
|
||
4. **Data Models** (model package) - Defines data structures
|
||
|
||
### Request Flow
|
||
|
||
1. Client submits code via `/submit` endpoint
|
||
2. Request is validated and assigned a unique ID
|
||
3. Submission is added to the job queue
|
||
4. Worker picks job from queue when available
|
||
5. Code is executed in appropriate Docker container
|
||
6. Results are stored and available via `/result` endpoint
|
||
|
||
### Dependency Diagram
|
||
|
||
```
|
||
Client Request → HTTP Handlers → Execution Service → Job Queue → Docker Containers
|
||
↑
|
||
Data Models
|
||
```
|
||
|
||
## Installation
|
||
|
||
### Prerequisites
|
||
|
||
- Go 1.22+
|
||
- Docker Engine
|
||
- Docker images for supported languages:
|
||
- `python:3.9`
|
||
- `eclipse-temurin:11-jdk-alpine`
|
||
- `gcc:latest`
|
||
|
||
### Setup Instructions
|
||
|
||
1. Clone the repository:
|
||
```bash
|
||
git clone https://github.com/arnab-afk/monaco.git
|
||
cd monaco/backend
|
||
```
|
||
|
||
2. Install Go dependencies:
|
||
```bash
|
||
go mod download
|
||
```
|
||
|
||
3. Build the application:
|
||
```bash
|
||
go build -o monaco main.go
|
||
```
|
||
|
||
4. Run the service:
|
||
```bash
|
||
./monaco
|
||
```
|
||
|
||
The service will start on port 8080 by default.
|
||
|
||
## API Reference
|
||
|
||
### Endpoints
|
||
|
||
#### `POST /submit`
|
||
|
||
Submits code for execution.
|
||
|
||
**Request Body:**
|
||
```json
|
||
{
|
||
"language": "python", // Required: "python", "java", "c", or "cpp"
|
||
"code": "print('Hello, World!')", // Required: source code to execute
|
||
"input": "optional input string" // Optional: input to stdin
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1" // Unique ID for this submission
|
||
}
|
||
```
|
||
|
||
**Status Codes:**
|
||
- 202 Accepted - Code accepted for execution
|
||
- 400 Bad Request - Invalid request (e.g., unsupported language)
|
||
|
||
#### `GET /status?id={submissionId}`
|
||
|
||
Checks the status of a submission.
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
|
||
"status": "completed", // "pending", "queued", "running", "completed", "failed"
|
||
"queuedAt": "2025-03-25T14:30:00Z",
|
||
"startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed"
|
||
"completedAt": "2025-03-25T14:30:02Z", // Only present if status is "completed" or "failed"
|
||
"executionTime": 1000 // Execution time in milliseconds (only if completed)
|
||
}
|
||
```
|
||
|
||
**Status Codes:**
|
||
- 200 OK - Status retrieved successfully
|
||
- 400 Bad Request - Missing ID parameter
|
||
- 404 Not Found - Submission with given ID not found
|
||
|
||
#### `GET /result?id={submissionId}`
|
||
|
||
Gets the execution result of a submission.
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
|
||
"status": "completed",
|
||
"language": "python",
|
||
"output": "Hello, World!",
|
||
"queuedAt": "2025-03-25T14:30:00Z",
|
||
"startedAt": "2025-03-25T14:30:01Z",
|
||
"completedAt": "2025-03-25T14:30:02Z",
|
||
"executionTime": 1000,
|
||
"executionTimeFormatted": "1.0s",
|
||
"totalTime": 2000,
|
||
"totalTimeFormatted": "2.0s"
|
||
}
|
||
```
|
||
|
||
**Status Codes:**
|
||
- 200 OK - Result retrieved successfully
|
||
- 400 Bad Request - Missing ID parameter
|
||
- 404 Not Found - Submission with given ID not found
|
||
|
||
#### `GET /queue-stats`
|
||
|
||
Gets statistics about the job queue.
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"queue_stats": {
|
||
"queue_length": 5,
|
||
"max_workers": 3,
|
||
"running_jobs": 3
|
||
},
|
||
"submissions": 42
|
||
}
|
||
```
|
||
|
||
## Code Execution
|
||
|
||
### Execution Process
|
||
|
||
1. **Language Detection**: The system identifies the programming language of the submission.
|
||
2. **Environment Setup**: A temporary directory is created for compiled languages.
|
||
3. **Container Setup**: Docker containers are configured with resource limits.
|
||
4. **Compilation**: For compiled languages (Java, C, C++), code is compiled first.
|
||
5. **Execution**: The program is executed with the provided input.
|
||
6. **Resource Monitoring**: Memory and CPU usage are limited during execution.
|
||
7. **Result Collection**: Output and errors are captured and stored.
|
||
|
||
### Language-Specific Processing
|
||
|
||
#### Python
|
||
- Directly executes Python code using the `-c` flag
|
||
- Uses `python:3.9` Docker image
|
||
- Resource limits: 100MB memory, 10% CPU
|
||
|
||
#### Java
|
||
- Detects class name using regex pattern matching
|
||
- Compiles with `javac` and runs with optimized JVM settings
|
||
- Uses `eclipse-temurin:11-jdk-alpine` Docker image
|
||
- Resource limits: 400MB memory, 50% CPU
|
||
- JVM flags: `-XX:+TieredCompilation`, `-XX:TieredStopAtLevel=1`, `-Xverify:none`
|
||
|
||
#### C/C++
|
||
- Saves code to a file in a temporary directory
|
||
- Compiles with `gcc`/`g++` and runs the executable
|
||
- Uses `gcc:latest` Docker image
|
||
- Resource limits: 100MB memory, 10% CPU
|
||
|
||
### Timeout Handling
|
||
|
||
All executions have a timeout limit:
|
||
- Python: 10 seconds
|
||
- Java: 15 seconds
|
||
- C/C++: 10 seconds
|
||
|
||
If execution exceeds this limit, the process is killed and an error is returned.
|
||
|
||
## Job Queue System
|
||
|
||
### Worker Pool
|
||
|
||
Monaco uses a worker pool to manage concurrent code executions:
|
||
|
||
- Default pool size: 20 workers (configurable)
|
||
- Maximum queue capacity: 100 jobs
|
||
- FIFO (First-In-First-Out) processing order
|
||
|
||
### Job Lifecycle
|
||
|
||
1. **Creation**: Job created when code is submitted
|
||
2. **Queuing**: Job added to queue with `queued` status
|
||
3. **Execution**: Worker picks job from queue and changes status to `running`
|
||
4. **Completion**: Job finishes with either `completed` or `failed` status
|
||
|
||
### Performance Metrics
|
||
|
||
The queue tracks and reports:
|
||
- Current queue length
|
||
- Number of running jobs
|
||
- Maximum worker count
|
||
- Total number of submissions
|
||
|
||
## Language Support
|
||
|
||
### Python
|
||
- **Version**: Python 3.9
|
||
- **Input Handling**: Direct stdin piping
|
||
- **Limitations**: No file I/O, no package imports outside standard library
|
||
|
||
### Java
|
||
- **Version**: Java 11 (Eclipse Temurin)
|
||
- **Class Detection**: Extracts class name from code using regex
|
||
- **Memory Settings**: 64MB min heap, 256MB max heap
|
||
- **Best Practices**: Use `public class` with the main method
|
||
|
||
### C
|
||
- **Version**: Latest GCC
|
||
- **Compilation Flags**: Default GCC settings
|
||
- **Execution**: Compiled binary
|
||
|
||
### C++
|
||
- **Version**: Latest G++
|
||
- **Standard**: C++17
|
||
- **Execution**: Compiled binary
|
||
|
||
## Security Considerations
|
||
|
||
### Containerization
|
||
|
||
All code execution happens within isolated Docker containers with:
|
||
- No network access (`--network=none`)
|
||
- Limited CPU and memory resources
|
||
- Limited file system access
|
||
- No persistent storage
|
||
|
||
### Resource Limiting
|
||
|
||
- **Memory Limits**: 100-400MB depending on language
|
||
- **CPU Limits**: 10-50% of CPU depending on language
|
||
- **File Descriptors**: Limited to 64 for Python
|
||
- **Execution Time**: Enforced timeouts (10-15 seconds)
|
||
|
||
### Known Limitations
|
||
|
||
- Container escape vulnerabilities
|
||
- Docker daemon security depends on host configuration
|
||
- Resource limits can be circumvented with certain techniques
|
||
|
||
## Configuration
|
||
|
||
The service can be configured through environment variables:
|
||
|
||
- `PORT`: HTTP port (default: 8080)
|
||
- `MAX_WORKERS`: Maximum concurrent executions (default: 3)
|
||
- `QUEUE_SIZE`: Maximum queue size (default: 100)
|
||
- `DEFAULT_LANGUAGE`: Default language if none specified (default: "python")
|
||
|
||
## Testing
|
||
|
||
### Unit Tests
|
||
|
||
Run unit tests with:
|
||
```bash
|
||
go test ./...
|
||
```
|
||
|
||
# Monaco Backend Test Plan
|
||
|
||
## Overview
|
||
This test plan outlines the testing approach for the Monaco code execution backend service.
|
||
|
||
## Test Environment
|
||
- Development: Local workstations with Docker and Go
|
||
- Testing: Dedicated test server with Docker
|
||
- Production-like: Staging environment with similar resources to production
|
||
|
||
## Test Types
|
||
|
||
### Unit Tests
|
||
- **Purpose**: Verify individual components work as expected
|
||
- **Components to Test**:
|
||
- Handler package
|
||
- Queue package
|
||
- Execution service
|
||
- Models
|
||
- **Tools**: Go testing framework
|
||
|
||
### Integration Tests
|
||
- **Purpose**: Verify components work together correctly
|
||
- **Focus Areas**:
|
||
- API endpoints
|
||
- End-to-end code execution flow
|
||
- Error handling
|
||
- **Tools**: Go testing framework, HTTP test utilities
|
||
|
||
### Load Tests
|
||
- **Purpose**: Verify system performance under load
|
||
- **Scenarios**:
|
||
- Concurrent submissions
|
||
- Mixed language workloads
|
||
- Queue saturation
|
||
- **Metrics**:
|
||
- Request throughput
|
||
- Response times
|
||
- Success rates
|
||
- Resource utilization
|
||
- **Tools**: Custom Python test scripts
|
||
|
||
## Test Data
|
||
- Simple programs in each language
|
||
- Programs with input requirements
|
||
- Programs with compile errors
|
||
- Programs with runtime errors
|
||
- Programs with timeouts
|
||
|
||
## Test Execution
|
||
1. Run unit tests on every code change
|
||
2. Run integration tests before merging to main branch
|
||
3. Run load tests weekly and before major releases
|
||
|
||
## Success Criteria
|
||
- All unit tests pass
|
||
- Integration tests complete successfully
|
||
- Load tests show acceptable performance metrics:
|
||
- 95% of requests complete successfully
|
||
- 95th percentile response time < 5 seconds
|
||
- System can handle 20 concurrent users
|
||
|
||
## Reporting
|
||
- Test results stored in CI/CD pipeline
|
||
- Performance metrics graphed over time
|
||
- Issues logged in GitHub issues
|
||
|
||
### Load Testing
|
||
|
||
A Python script (`test.py`) is included for load testing:
|
||
```bash
|
||
python test.py
|
||
```
|
||
|
||
This script sends 500 requests concurrently and reports performance metrics.
|
||
|
||
### Manual Testing with Curl
|
||
|
||
#### Python Example
|
||
```bash
|
||
curl -X POST http://localhost:8080/submit \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"language": "python",
|
||
"code": "print(\"Hello, World!\")\nfor i in range(5):\n print(f\"Number: {i}\")",
|
||
"input": ""
|
||
}'
|
||
```
|
||
|
||
#### Java Example
|
||
```bash
|
||
curl -X POST http://localhost:8080/submit \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"language": "java",
|
||
"code": "public class Solution {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n for (int i = 0; i < 5; i++) {\n System.out.println(\"Number: \" + i);\n }\n }\n}",
|
||
"input": ""
|
||
}'
|
||
```
|
||
|
||
## Performance Tuning
|
||
|
||
### Optimizing Worker Count
|
||
|
||
The optimal worker count depends on:
|
||
- CPU cores available
|
||
- Memory available
|
||
- Docker container startup time
|
||
|
||
For most single-server deployments, 3-5 workers is optimal.
|
||
|
||
### Memory Considerations
|
||
|
||
Each language has different memory requirements:
|
||
- Python: ~50-100MB per instance
|
||
- Java: ~200-400MB per instance
|
||
- C/C++: ~50-100MB per instance
|
||
|
||
Calculate total memory needs as: `(Python instances × 100MB) + (Java instances × 400MB) + (C/C++ instances × 100MB)`
|
||
|
||
### Disk Space Management
|
||
|
||
Temporary files are cleaned up after execution, but with high request volumes, ensure adequate disk space for concurrent operations (approximately 1-5MB per request for compiled languages).
|
||
|
||
## Troubleshooting
|
||
|
||
### Common Issues
|
||
|
||
#### Docker Connection Errors
|
||
```
|
||
Error: Cannot connect to the Docker daemon
|
||
```
|
||
**Solution**: Ensure Docker daemon is running with `systemctl start docker` or `docker --version`
|
||
|
||
#### Permissions Issues
|
||
```
|
||
Error: Permission denied while trying to connect to the Docker daemon socket
|
||
```
|
||
**Solution**: Add user to docker group: `sudo usermod -aG docker $USER`
|
||
|
||
#### Container Resource Limits
|
||
```
|
||
Error: Container killed due to memory limit
|
||
```
|
||
**Solution**: Increase memory limits in execution service or optimize submitted code
|
||
|
||
#### File Not Found Errors
|
||
```
|
||
Error: Failed to write Java file
|
||
```
|
||
**Solution**: Check temporary directory permissions and disk space
|
||
|
||
### Logs
|
||
|
||
The service provides structured logs with prefixes for easier filtering:
|
||
- `[HTTP]` - API requests
|
||
- `[QUEUE]` - Queue operations
|
||
- `[WORKER-n]` - Worker activities
|
||
- `[EXEC-id]` - Execution details
|
||
- `[PYTHON/JAVA/C/CPP-id]` - Language-specific logs
|
||
- `[TIMEOUT-id]` - Timeout events
|
||
- `[RESULT-id]` - Execution results
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||
|
||
## Contributing
|
||
|
||
Contributions are welcome! Please feel free to submit a Pull Request.
|