Skip to content

Commit 1f76158

Browse files
committed
Initial commit: Flask backend, Dockerfile, GKE workflow template, and documentation template
0 parents  commit 1f76158

File tree

11 files changed

+35038
-0
lines changed

11 files changed

+35038
-0
lines changed

.github/workflows/gke-cd.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Backend CI/CD to GKE
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build-and-push-docker:
11+
name: Build and Push Docker Image
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v3
17+
18+
- name: Set up Google Cloud SDK
19+
uses: google-github-actions/setup-gcloud@v1
20+
with:
21+
service_account_key: ${{ secrets.GKE_SA_KEY }} # Configure this secret in GitHub Actions
22+
project_id: ${{ secrets.GKE_PROJECT_ID }} # Configure this secret in GitHub Actions
23+
24+
- name: Enable Docker Credential Helper
25+
run: |-
26+
gcloud auth configure-docker
27+
28+
- name: Build Docker image
29+
run: |-
30+
docker build --tag gcr.io/${{ secrets.GKE_PROJECT_ID }}/mi-backend-simulacion:${GITHUB_SHA} .
31+
32+
- name: Push Docker image to GCR
33+
run: |-
34+
docker push gcr.io/${{ secrets.GKE_PROJECT_ID }}/mi-backend-simulacion:${GITHUB_SHA}
35+
36+
deploy-to-gke:
37+
name: Deploy to GKE
38+
needs: build-and-push-docker
39+
runs-on: ubuntu-latest
40+
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v3
44+
45+
- name: Set up Google Cloud SDK
46+
uses: google-github-actions/setup-gcloud@v1
47+
with:
48+
service_account_key: ${{ secrets.GKE_SA_KEY }} # Configure this secret in GitHub Actions
49+
project_id: ${{ secrets.GKE_PROJECT_ID }} # Configure this secret in GitHub Actions
50+
cluster_name: tu-cluster-gke # Reemplaza with your GKE cluster name
51+
cluster_zone: tu-zona-gke # Reemplaza with your GKE cluster zone
52+
53+
- name: Get Kubernetes credentials
54+
run: |-
55+
gcloud container clusters get-credentials tu-cluster-gke --zone tu-zona-gke --project ${{ secrets.GKE_PROJECT_ID }} # Reemplaza with your cluster and zone
56+
57+
- name: Deploy to GKE (Ejemplo Básico - Necesitas Adaptar!)
58+
run: |-
59+
# This is a VERY basic example. YOU MUST ADAPT THIS TO YOUR KUBERNETES DEPLOYMENT.
60+
# Normally you would use a Kubernetes Deployment YAML file and kubectl apply -f deployment.yaml
61+
# Here, we just update the image of an existing Deployment named 'mi-backend-deployment' (example).
62+
kubectl set image deployment/mi-backend-deployment mi-backend-container=gcr.io/${{ secrets.GKE_PROJECT_ID }}/mi-backend-simulacion:${GITHUB_SHA}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
simulation.out

Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.9-slim-buster
2+
WORKDIR /app
3+
4+
COPY requirements.txt ./
5+
RUN pip install --no-cache-dir -r requirements.txt
6+
7+
8+
COPY backend.py ./
9+
COPY src ./src
10+
COPY networks ./networks
11+
COPY bitrates ./bitrates
12+
13+
14+
RUN g++ -O3 -o simulation.out main.cpp
15+
16+
# - 'backend:app' especifica el módulo 'backend.py' y la instancia de Flask 'app'.
17+
# - '--bind 0.0.0.0:8080' hace que Gunicorn escuche en todas las interfaces (0.0.0.0) en el puerto 8080 dentro del contenedor.
18+
# - '--workers 3' configura Gunicorn para usar 3 procesos de trabajo para manejar las peticiones.
19+
CMD ["gunicorn", "backend:app", "--bind", "0.0.0.0:8080", "--workers", "3"]

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Flask Simulation Backend API
2+
3+
This is a Flask-based backend API that runs C++ library Flex Net Sim.
4+
5+
## Prerequisites
6+
7+
* Python 3.9 or higher
8+
* g++ (GNU C++ Compiler)
9+
* Docker (for containerization)
10+
* Google Cloud SDK (for deployment to GKE)
11+
* A Google Cloud Project with Google Kubernetes Engine (GKE) and Google Container Registry (GCR) enabled.
12+
13+
## Getting Started (Local Development)
14+
15+
1. **Clone the repository:**
16+
```bash
17+
git clone [repository-url]
18+
cd flask-simulation-backend
19+
```
20+
21+
2. **Create a Python virtual environment (recommended):**
22+
```bash
23+
python3 -m venv .venv
24+
source .venv/bin/activate # On Linux/macOS
25+
.venv\Scripts\activate # On Windows
26+
```
27+
28+
3. **Install Python dependencies:**
29+
```bash
30+
pip install -r requirements.txt
31+
```
32+
33+
4. **Compile the C++ simulation (this happens automatically on startup):**
34+
When you run the Flask backend for the first time, it will compile `main.cpp` to `simulation.out`.
35+
36+
5. **Run the Flask backend:**
37+
```bash
38+
python backend.py
39+
```
40+
The backend will be accessible at `http://127.0.0.1:5000`.
41+
42+
6. **Send simulation requests using `curl` or a frontend application:**
43+
Example `curl` request:
44+
```bash
45+
curl -X POST -H "Content-Type: application/json" -d '{"algorithm": "FirstFit", "networkType": 1, "bitrate": "bitrate"}' [http://127.0.0.1:5000/run_simulation](http://127.0.0.1:5000/run_simulation)
46+
```
47+
48+
## Dockerization
49+
50+
To build the Docker image:
51+
52+
```bash
53+
docker build -t mi-backend-simulacion .

backend.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# backend.py (Flask API)
2+
from flask import Flask, request, jsonify
3+
import subprocess
4+
import os
5+
import logging # Import the logging module
6+
from flask_cors import CORS # Import Flask-CORS (if you use it)
7+
8+
# --- Flask Setup ---
9+
app = Flask(__name__)
10+
CORS(app, resources={r"/run_simulation": {"origins": 'http://localhost:5000'}}) # <-- UNCOMMENT and SET ORIGIN
11+
12+
# --- Setup and Compilation ---
13+
SIMULATION_EXECUTABLE = "./src/simulation.out"
14+
COMPILE_ERROR = None # Global variable to store compilation errors
15+
16+
# --- Logging Setup ---
17+
logging.basicConfig(level=logging.INFO, # Set the minimum level to INFO (or DEBUG for more verbosity)
18+
format='%(asctime)s - %(levelname)s - %(message)s')
19+
logger = logging.getLogger(__name__) # Get a logger instance for this module
20+
21+
# --- Compilation ---
22+
def compile_simulation():
23+
"""Compiles the simulation at startup. Logs compilation details."""
24+
global COMPILE_ERROR
25+
26+
# If file exists, delete it.
27+
if os.path.exists(SIMULATION_EXECUTABLE):
28+
logger.info(f"{SIMULATION_EXECUTABLE} already exists, deleting.") # Use logger.info instead of print
29+
os.remove(SIMULATION_EXECUTABLE)
30+
31+
logger.info("Compiling simulation...") # Use logger.info
32+
compile_result = subprocess.run(["g++", "-O3", "-o", SIMULATION_EXECUTABLE, "./src/main.cpp"], capture_output=True, text=True)
33+
if compile_result.returncode != 0:
34+
COMPILE_ERROR = {"error": "Compilation failed", "details": compile_result.stderr}
35+
logger.error(f"Compilation failed: {COMPILE_ERROR['error']} - Details: {COMPILE_ERROR['details']}") # Use logger.error for errors
36+
return False
37+
logger.info("Simulation compiled successfully.") # Use logger.info
38+
return True
39+
40+
# --- Run Simulation Endpoint ---
41+
@app.route("/run_simulation", methods=["POST"])
42+
@app.route("/run_simulation", methods=["POST"])
43+
def run_simulation():
44+
"""Runs the simulation with parameters provided in the request."""
45+
global COMPILE_ERROR
46+
if COMPILE_ERROR:
47+
return jsonify(COMPILE_ERROR), 500 # Return compilation error if it exists from startup
48+
49+
if not os.path.exists(SIMULATION_EXECUTABLE): # Double check if executable exists
50+
return jsonify({"error": "Simulation executable not found. Contact developer."}), 400
51+
52+
try:
53+
data = request.get_json()
54+
if not data:
55+
return jsonify({"error": "Missing JSON parameters in request body."}), 400
56+
57+
# Parameters, use default values if not provided
58+
algorithm = data.get("algorithm", "FirstFit")
59+
networkType = data.get("networkType", 1)
60+
goal_connections = data.get("goal_connections", 100000)
61+
confidence = data.get("confidence", 0.05)
62+
lambda_param = data.get("lambda", 1)
63+
mu = data.get("mu", 10)
64+
network = data.get("network", "NSFNet")
65+
bitrate = data.get("bitrate", "bitrate")
66+
67+
# Construct command for subprocess
68+
command = [
69+
f"./{SIMULATION_EXECUTABLE}",
70+
str(algorithm),
71+
str(networkType),
72+
str(goal_connections),
73+
str(confidence),
74+
str(lambda_param),
75+
str(mu),
76+
str(network),
77+
str(bitrate)
78+
]
79+
logger.debug(f"Running simulation with command: {' '.join(command)}") # Debug log for command
80+
81+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
82+
output, error = process.communicate()
83+
84+
if process.returncode != 0:
85+
logger.error(f"Simulation execution failed. Return code: {process.returncode}, Details: {error.strip()}") # Error log for simulation failure
86+
return jsonify({"error": "Simulation execution failed", "details": error.strip()}), 500
87+
88+
# Return output in a structured JSON format
89+
return jsonify({"output": output.strip(), "error": error.strip()}), 200
90+
91+
except Exception as e:
92+
logger.exception("Unexpected error during run_simulation:")
93+
return jsonify({"error": "An unexpected error occurred. Contact developer.", "details": str(e)}), 500
94+
95+
# --- Run Setup on Application Start ---
96+
with app.app_context(): # Context needed to call route function outside request
97+
compile_success = compile_simulation() # Call compile directly at startup
98+
99+
if __name__ == "__main__":
100+
if not compile_success:
101+
print("Application start aborted due to compilation error. Please check /setup endpoint.")
102+
else:
103+
print("Starting Flask application...")
104+
app.run(debug=True, host="0.0.0.0") # Enable debug for development

bitrates/bitrate.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"10": [
3+
{
4+
"BPSK": {
5+
"slots": 1,
6+
"reach": 5520
7+
}
8+
}
9+
],
10+
"40": [
11+
{
12+
"BPSK": {
13+
"slots": 4,
14+
"reach": 5520
15+
}
16+
},
17+
{
18+
"QPSK": {
19+
"slots": 2,
20+
"reach": 5520
21+
}
22+
}
23+
],
24+
"100": [
25+
{
26+
"BPSK": {
27+
"slots": 8,
28+
"reach": 5520
29+
}
30+
}
31+
],
32+
"400": [
33+
{
34+
"BPSK": {
35+
"slots": 32,
36+
"reach": 5520
37+
}
38+
}
39+
],
40+
"1000": [
41+
{
42+
"BPSK": {
43+
"slots": 80,
44+
"reach": 5520
45+
}
46+
}
47+
]
48+
}

0 commit comments

Comments
 (0)