mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Initialize telecom platform with core services and infrastructure
- Add Go-based API server and carrier connector services - Add Rust-based charging engine and packet gateway services - Add Docker Compose configuration for local development - Add comprehensive documentation (API, Architecture, Deployment) - Add development scripts for Redis, MongoDB, and environment setup - Add workspace configuration for Go modules and pnpm - Remove unnecessary documentation files from web-dashboard - Add project build configuration (Makefile, Cargo.toml) This commit establishes the foundational microservices architecture for the sovereign Telecom-as-a-Service platform.
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
|||||||
|
# Go
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
/bin/
|
||||||
|
/dist/
|
||||||
|
go.work.sum
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
*.pdb
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# Node/TypeScript
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
dist/
|
||||||
|
.turbo/
|
||||||
|
.vercel/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.elf
|
||||||
|
*.wasm
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
.tmp/
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"apps/charging-engine",
|
||||||
|
"apps/packet-gateway",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["TaaS Team <[email protected]>"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.94"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
# Async runtime
|
||||||
|
tokio = { version = "1.40", features = ["full"] }
|
||||||
|
async-std = "1.13"
|
||||||
|
|
||||||
|
# eBPF with Aya (for packet-gateway)
|
||||||
|
aya = { version = "0.13", features = ["async_tokio"] }
|
||||||
|
aya-ebpf = "0.1"
|
||||||
|
aya-log = "0.2"
|
||||||
|
aya-log-ebpf = "0.1"
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
# Database
|
||||||
|
redis = { version = "0.27", features = ["tokio-comp", "connection-manager", "cluster-async"] }
|
||||||
|
|
||||||
|
# Web framework (for charging-engine)
|
||||||
|
axum = { version = "0.7", features = ["macros"] }
|
||||||
|
tower = "0.5"
|
||||||
|
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||||
|
hyper = "1.5"
|
||||||
|
|
||||||
|
# Networking
|
||||||
|
bytes = "1.7"
|
||||||
|
prost = "0.13"
|
||||||
|
|
||||||
|
# Logging and tracing
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
anyhow = "1.0"
|
||||||
|
thiserror = "2.0"
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
dotenv = "0.15"
|
||||||
|
config = "0.14"
|
||||||
|
|
||||||
|
# Time handling
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
uuid = { version = "1.10", features = ["v4", "serde"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
strip = true
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 TaaS Platform Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# Master Makefile - Telecom Platform
|
||||||
|
# Orchestrates builds across Go, Rust, and TypeScript
|
||||||
|
|
||||||
|
.PHONY: all build-ui build-rust build-go clean test dev help install-deps
|
||||||
|
|
||||||
|
# Colors for terminal output
|
||||||
|
CYAN := \033[0;36m
|
||||||
|
GREEN := \033[0;32m
|
||||||
|
YELLOW := \033[1;33m
|
||||||
|
RESET := \033[0m
|
||||||
|
|
||||||
|
help: ## Show this help message
|
||||||
|
@echo "$(CYAN)Telecom Platform - Available Commands:$(RESET)"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||||
|
awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-20s$(RESET) %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
all: build-ui build-rust build-go ## Build all components
|
||||||
|
|
||||||
|
install-deps: ## Install all dependencies
|
||||||
|
@echo "$(CYAN)Installing Go dependencies...$(RESET)"
|
||||||
|
@cd apps/api-server && go mod download
|
||||||
|
@cd apps/carrier-connector && go mod download
|
||||||
|
@echo "$(CYAN)Installing Rust dependencies...$(RESET)"
|
||||||
|
@cargo fetch
|
||||||
|
@echo "$(CYAN)Installing Node.js dependencies...$(RESET)"
|
||||||
|
@pnpm install
|
||||||
|
@echo "$(GREEN)All dependencies installed!$(RESET)"
|
||||||
|
|
||||||
|
# TypeScript - Frontend & SDK
|
||||||
|
build-ui: ## Build Next.js dashboard and TypeScript SDK
|
||||||
|
@echo "$(CYAN)Building TypeScript projects...$(RESET)"
|
||||||
|
@pnpm install
|
||||||
|
@pnpm -r run build
|
||||||
|
@echo "$(GREEN)TypeScript build complete!$(RESET)"
|
||||||
|
|
||||||
|
dev-ui: ## Start Next.js development server
|
||||||
|
@pnpm --filter web-dashboard dev
|
||||||
|
|
||||||
|
lint-ui: ## Lint TypeScript code
|
||||||
|
@pnpm -r run lint
|
||||||
|
|
||||||
|
# Rust - Data Plane & Charging Engine
|
||||||
|
build-rust: ## Build Rust components (release mode)
|
||||||
|
@echo "$(CYAN)Building Rust projects...$(RESET)"
|
||||||
|
@cargo build --release
|
||||||
|
@echo "$(GREEN)Rust build complete!$(RESET)"
|
||||||
|
|
||||||
|
build-rust-dev: ## Build Rust components (debug mode)
|
||||||
|
@cargo build
|
||||||
|
|
||||||
|
test-rust: ## Run Rust tests
|
||||||
|
@cargo test --workspace
|
||||||
|
|
||||||
|
lint-rust: ## Run Rust linter (clippy)
|
||||||
|
@cargo clippy --workspace -- -D warnings
|
||||||
|
|
||||||
|
# Go - BSS API & Carrier Connector
|
||||||
|
build-go: ## Build Go services
|
||||||
|
@echo "$(CYAN)Building Go projects...$(RESET)"
|
||||||
|
@mkdir -p dist
|
||||||
|
@cd apps/api-server && go build -o ../../dist/api-server
|
||||||
|
@cd apps/carrier-connector && go build -o ../../dist/carrier-connector
|
||||||
|
@echo "$(GREEN)Go build complete!$(RESET)"
|
||||||
|
|
||||||
|
test-go: ## Run Go tests
|
||||||
|
@go test ./apps/...
|
||||||
|
|
||||||
|
lint-go: ## Run Go linter
|
||||||
|
@golangci-lint run ./apps/...
|
||||||
|
|
||||||
|
fmt-go: ## Format Go code
|
||||||
|
@go fmt ./apps/...
|
||||||
|
|
||||||
|
# Development
|
||||||
|
dev: ## Start development environment (info only)
|
||||||
|
@echo "$(CYAN)To start the development environment:$(RESET)"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(YELLOW)Terminal 1 - API Server:$(RESET)"
|
||||||
|
@echo " ./dist/api-server"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(YELLOW)Terminal 2 - Carrier Connector:$(RESET)"
|
||||||
|
@echo " ./dist/carrier-connector"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(YELLOW)Terminal 3 - Charging Engine:$(RESET)"
|
||||||
|
@echo " ./target/release/charging-engine"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(YELLOW)Terminal 4 - Web Dashboard:$(RESET)"
|
||||||
|
@echo " make dev-ui"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
test: test-go test-rust ## Run all tests
|
||||||
|
@echo "$(GREEN)All tests passed!$(RESET)"
|
||||||
|
|
||||||
|
# Linting
|
||||||
|
lint: lint-go lint-rust lint-ui ## Run all linters
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
fmt: fmt-go ## Format all code
|
||||||
|
|
||||||
|
# Cleaning
|
||||||
|
clean: ## Remove all build artifacts
|
||||||
|
@echo "$(CYAN)Cleaning build artifacts...$(RESET)"
|
||||||
|
@rm -rf dist/
|
||||||
|
@rm -rf target/
|
||||||
|
@pnpm -r exec rm -rf .next dist node_modules
|
||||||
|
@find . -name "*.o" -type f -delete
|
||||||
|
@find . -name "*.a" -type f -delete
|
||||||
|
@echo "$(GREEN)Clean complete!$(RESET)"
|
||||||
|
|
||||||
|
clean-all: clean ## Remove all artifacts including dependencies
|
||||||
|
@rm -rf node_modules
|
||||||
|
@rm -rf apps/*/node_modules
|
||||||
|
@rm -rf libs/*/node_modules
|
||||||
|
@cd apps/api-server && rm -rf vendor/
|
||||||
|
@cd apps/carrier-connector && rm -rf vendor/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-build: ## Build Docker images for all services
|
||||||
|
@echo "$(CYAN)Building Docker images...$(RESET)"
|
||||||
|
@docker build -f deployments/docker/api-server.Dockerfile -t taas-api-server .
|
||||||
|
@docker build -f deployments/docker/carrier-connector.Dockerfile -t taas-carrier-connector .
|
||||||
|
@docker build -f deployments/docker/charging-engine.Dockerfile -t taas-charging-engine .
|
||||||
|
@docker build -f deployments/docker/web-dashboard.Dockerfile -t taas-web-dashboard .
|
||||||
|
@echo "$(GREEN)Docker images built!$(RESET)"
|
||||||
|
|
||||||
|
docker-up: ## Start all services with docker-compose
|
||||||
|
@docker-compose up -d
|
||||||
|
|
||||||
|
docker-down: ## Stop all services
|
||||||
|
@docker-compose down
|
||||||
|
|
||||||
|
# Kubernetes
|
||||||
|
k8s-deploy: ## Deploy to Kubernetes
|
||||||
|
@kubectl apply -f deployments/kubernetes/
|
||||||
|
|
||||||
|
k8s-delete: ## Delete Kubernetes resources
|
||||||
|
@kubectl delete -f deployments/kubernetes/
|
||||||
|
|
||||||
|
# Database setup
|
||||||
|
db-setup: ## Set up MongoDB and Redis
|
||||||
|
@echo "$(CYAN)Setting up databases...$(RESET)"
|
||||||
|
@bash scripts/setup-mongodb.sh
|
||||||
|
@bash scripts/setup-redis.sh
|
||||||
|
@echo "$(GREEN)Database setup complete!$(RESET)"
|
||||||
|
|
||||||
|
# Free5GC integration
|
||||||
|
free5gc-setup: ## Set up free5GC (requires separate installation)
|
||||||
|
@echo "$(YELLOW)Please install free5GC separately.$(RESET)"
|
||||||
|
@echo "Visit: https://free5gc.org/guide/"
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
MONGODB_URI=mongodb://localhost:27017/free5gc
|
||||||
|
REDIS_URI=redis://localhost:6379
|
||||||
|
API_PORT=8000
|
||||||
|
GIN_MODE=debug
|
||||||
|
JWT_SECRET=change-me-in-production
|
||||||
|
LOG_LEVEL=info
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module github.com/taas-platform/telecom-platform/apps/api-server
|
||||||
|
|
||||||
|
go 1.26
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.12.0
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/rs/zerolog v1.35.0
|
||||||
|
go.mongodb.org/mongo-driver v1.17.9
|
||||||
|
go.opentelemetry.io/otel v1.43.0
|
||||||
|
)
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger zerolog.Logger
|
||||||
|
dbClient *mongo.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize logger
|
||||||
|
logger = zerolog.New(os.Stdout).With().
|
||||||
|
Timestamp().
|
||||||
|
Str("service", "api-server").
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
logger.Info().Msg("No .env file found, using system environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to MongoDB
|
||||||
|
mongoURI := getEnv("MONGODB_URI", "mongodb://localhost:27017/free5gc")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
dbClient, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal().Err(err).Msg("Failed to connect to MongoDB")
|
||||||
|
}
|
||||||
|
defer dbClient.Disconnect(context.Background())
|
||||||
|
|
||||||
|
// Verify connection
|
||||||
|
if err := dbClient.Ping(ctx, nil); err != nil {
|
||||||
|
logger.Fatal().Err(err).Msg("Failed to ping MongoDB")
|
||||||
|
}
|
||||||
|
logger.Info().Msg("Connected to MongoDB successfully")
|
||||||
|
|
||||||
|
// Initialize Gin router
|
||||||
|
router := setupRouter()
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
port := getEnv("API_PORT", "8000")
|
||||||
|
logger.Info().Str("port", port).Msg("Starting API server")
|
||||||
|
if err := router.Run(":" + port); err != nil {
|
||||||
|
logger.Fatal().Err(err).Msg("Failed to start server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRouter() *gin.Engine {
|
||||||
|
// Set Gin mode
|
||||||
|
if getEnv("GIN_MODE", "debug") == "release" {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
router.GET("/health", healthCheck)
|
||||||
|
|
||||||
|
// API v1 routes
|
||||||
|
v1 := router.Group("/v1")
|
||||||
|
{
|
||||||
|
// eSIM management
|
||||||
|
v1.POST("/esims", createESIM)
|
||||||
|
v1.GET("/esims/:id", getESIM)
|
||||||
|
v1.GET("/esims/:id/usage", getESIMUsage)
|
||||||
|
v1.DELETE("/esims/:id", deleteESIM)
|
||||||
|
|
||||||
|
// Subscriber management
|
||||||
|
v1.GET("/subscribers", listSubscribers)
|
||||||
|
v1.GET("/subscribers/:imsi", getSubscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
func healthCheck(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "healthy",
|
||||||
|
"service": "api-server",
|
||||||
|
"timestamp": time.Now().Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /v1/esims - Create new eSIM
|
||||||
|
func createESIM(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
DataPlan string `json:"data_plan" binding:"required"`
|
||||||
|
CountryCode string `json:"country_code" binding:"required"`
|
||||||
|
Carrier string `json:"carrier,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate IMSI (simplified - use proper allocation in production)
|
||||||
|
imsi := fmt.Sprintf("208930%09d", time.Now().UnixNano()%1000000000)
|
||||||
|
|
||||||
|
// Using Go 1.26's new(expr) for optional pointer fields
|
||||||
|
esim := gin.H{
|
||||||
|
"esim_id": uuid.New().String(),
|
||||||
|
"imsi": imsi,
|
||||||
|
"iccid": generateICCID(imsi),
|
||||||
|
"activation_code": fmt.Sprintf("LPA:1$smdp.example.com$%s", uuid.New().String()),
|
||||||
|
"status": "provisioned",
|
||||||
|
"data_plan": req.DataPlan,
|
||||||
|
"country_code": req.CountryCode,
|
||||||
|
"created_at": time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().
|
||||||
|
Str("esim_id", esim["esim_id"].(string)).
|
||||||
|
Str("imsi", imsi).
|
||||||
|
Msg("eSIM created")
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, esim)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /v1/esims/:id - Get eSIM details
|
||||||
|
func getESIM(c *gin.Context) {
|
||||||
|
esimID := c.Param("id")
|
||||||
|
|
||||||
|
// TODO: Implement actual database lookup
|
||||||
|
esim := gin.H{
|
||||||
|
"esim_id": esimID,
|
||||||
|
"imsi": "208930000000001",
|
||||||
|
"iccid": "8933123456789012345",
|
||||||
|
"status": "active",
|
||||||
|
"data_plan": "1GB",
|
||||||
|
"created_at": time.Now().Add(-24 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, esim)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /v1/esims/:id/usage - Get eSIM usage statistics
|
||||||
|
func getESIMUsage(c *gin.Context) {
|
||||||
|
esimID := c.Param("id")
|
||||||
|
|
||||||
|
usage := gin.H{
|
||||||
|
"esim_id": esimID,
|
||||||
|
"data_used": 250 * 1024 * 1024, // 250 MB in bytes
|
||||||
|
"data_limit": 1024 * 1024 * 1024, // 1 GB in bytes
|
||||||
|
"sessions": []gin.H{
|
||||||
|
{
|
||||||
|
"started_at": time.Now().Add(-2 * time.Hour),
|
||||||
|
"ended_at": time.Now().Add(-1 * time.Hour),
|
||||||
|
"bytes_up": 10 * 1024 * 1024,
|
||||||
|
"bytes_down": 50 * 1024 * 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE /v1/esims/:id - Terminate eSIM
|
||||||
|
func deleteESIM(c *gin.Context) {
|
||||||
|
esimID := c.Param("id")
|
||||||
|
|
||||||
|
logger.Info().Str("esim_id", esimID).Msg("eSIM terminated")
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "eSIM terminated successfully",
|
||||||
|
"esim_id": esimID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /v1/subscribers - List all subscribers
|
||||||
|
func listSubscribers(c *gin.Context) {
|
||||||
|
// TODO: Implement MongoDB query
|
||||||
|
subscribers := []gin.H{
|
||||||
|
{
|
||||||
|
"imsi": "208930000000001",
|
||||||
|
"status": "active",
|
||||||
|
"created_at": time.Now().Add(-7 * 24 * time.Hour),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"subscribers": subscribers,
|
||||||
|
"count": len(subscribers),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /v1/subscribers/:imsi - Get subscriber details
|
||||||
|
func getSubscriber(c *gin.Context) {
|
||||||
|
imsi := c.Param("imsi")
|
||||||
|
|
||||||
|
subscriber := gin.H{
|
||||||
|
"imsi": imsi,
|
||||||
|
"plmn_id": "20893",
|
||||||
|
"status": "active",
|
||||||
|
"created_at": time.Now().Add(-7 * 24 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
func getEnv(key, fallback string) string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateICCID(imsi string) string {
|
||||||
|
// ICCID format: 89 (Telecom) + CC (Country) + ISSUER + ACCOUNT + CHECK
|
||||||
|
// Simplified implementation
|
||||||
|
return fmt.Sprintf("893312%s%d", imsi[5:14], time.Now().Unix()%10)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
module github.com/taas-platform/telecom-platform/apps/carrier-connector
|
||||||
|
|
||||||
|
go 1.26
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-resty/resty/v2 v2.15.3
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/rs/zerolog v1.33.0
|
||||||
|
)
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger zerolog.Logger
|
||||||
|
|
||||||
|
type ES2Client struct {
|
||||||
|
baseURL string
|
||||||
|
apiKey string
|
||||||
|
httpClient *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileOrder struct {
|
||||||
|
ICCID string `json:"iccid"`
|
||||||
|
IMSI string `json:"imsi"`
|
||||||
|
K string `json:"k"`
|
||||||
|
OPc string `json:"opc"`
|
||||||
|
MCC string `json:"mcc"`
|
||||||
|
MNC string `json:"mnc"`
|
||||||
|
ProfileType string `json:"profileType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileResponse struct {
|
||||||
|
ActivationCode string `json:"activationCode"`
|
||||||
|
ProfileID string `json:"profileId"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize logger
|
||||||
|
logger = zerolog.New(os.Stdout).With().
|
||||||
|
Timestamp().
|
||||||
|
Str("service", "carrier-connector").
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
logger.Info().Msg("No .env file found, using system environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("Carrier Connector started")
|
||||||
|
|
||||||
|
// Initialize ES2+ client
|
||||||
|
smdpURL := getEnv("SMDP_URL", "https://smdp.example.com")
|
||||||
|
apiKey := getEnv("SMDP_API_KEY", "test-api-key")
|
||||||
|
|
||||||
|
client := NewES2Client(smdpURL, apiKey)
|
||||||
|
|
||||||
|
// Example: Order a profile
|
||||||
|
order := &ProfileOrder{
|
||||||
|
ICCID: "8933123456789012345",
|
||||||
|
IMSI: "208930000000001",
|
||||||
|
K: "465B5CE8B199B49FAA5F0A2EE238A6BC",
|
||||||
|
OPc: "E8ED289DEBA952E4283B54E88E6183CA",
|
||||||
|
MCC: "208",
|
||||||
|
MNC: "93",
|
||||||
|
ProfileType: "operational",
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().
|
||||||
|
Str("imsi", order.IMSI).
|
||||||
|
Msg("Ordering eSIM profile from SM-DP+")
|
||||||
|
|
||||||
|
// In production, this would be triggered by API requests
|
||||||
|
response, err := client.OrderProfile(order)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Failed to order profile")
|
||||||
|
} else {
|
||||||
|
logger.Info().
|
||||||
|
Str("activation_code", response.ActivationCode).
|
||||||
|
Str("profile_id", response.ProfileID).
|
||||||
|
Msg("Profile ordered successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep service running
|
||||||
|
logger.Info().Msg("Carrier Connector running. Press Ctrl+C to exit.")
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewES2Client(baseURL, apiKey string) *ES2Client {
|
||||||
|
client := resty.New().
|
||||||
|
SetBaseURL(baseURL).
|
||||||
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", apiKey)).
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetTimeout(10 * time.Second).
|
||||||
|
SetTLSClientConfig(&tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &ES2Client{
|
||||||
|
baseURL: baseURL,
|
||||||
|
apiKey: apiKey,
|
||||||
|
httpClient: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ES2Client) OrderProfile(order *ProfileOrder) (*ProfileResponse, error) {
|
||||||
|
var response ProfileResponse
|
||||||
|
|
||||||
|
// NOTE: This is a simplified example
|
||||||
|
// In production, implement full GSMA ES2+ protocol
|
||||||
|
resp, err := c.httpClient.R().
|
||||||
|
SetBody(order).
|
||||||
|
SetResult(&response).
|
||||||
|
Post("/gsma/rsp2/es2plus/createProfile")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ES2+ API call failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode() != 200 {
|
||||||
|
return nil, fmt.Errorf("SM-DP+ returned error: %s", resp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, fallback string) string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
REDIS_URI=redis://127.0.0.1/
|
||||||
|
SERVER_PORT=8080
|
||||||
|
RUST_LOG=info
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "charging-engine"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { workspace = true }
|
||||||
|
redis = { workspace = true }
|
||||||
|
axum = { workspace = true }
|
||||||
|
tower = { workspace = true }
|
||||||
|
tower-http = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
dotenv = { workspace = true }
|
||||||
|
uuid = { workspace = true }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "charging-engine"
|
||||||
|
path = "src/main.rs"
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
redis: redis::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
// Initialize tracing
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_target(false)
|
||||||
|
.compact()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
|
// Connect to Redis
|
||||||
|
let redis_url = std::env::var("REDIS_URI").unwrap_or_else(|_| "redis://127.0.0.1/".to_string());
|
||||||
|
let redis_client = redis::Client::open(redis_url)?;
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
let mut conn = redis_client.get_multiplexed_async_connection().await?;
|
||||||
|
let _: () = redis::cmd("PING").query_async(&mut conn).await?;
|
||||||
|
info!("Connected to Redis successfully");
|
||||||
|
|
||||||
|
let state = Arc::new(AppState {
|
||||||
|
redis: redis_client,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build HTTP API with CORS
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.allow_origin(Any)
|
||||||
|
.allow_methods(Any)
|
||||||
|
.allow_headers(Any);
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/v1/credit/:ip/check", post(check_credit))
|
||||||
|
.route("/v1/credit/:ip/deduct", post(deduct_credit))
|
||||||
|
.route("/v1/credit/:ip/add", post(add_credit))
|
||||||
|
.route("/v1/credit/:ip/balance", get(get_balance))
|
||||||
|
.route("/health", get(health_check))
|
||||||
|
.layer(cors)
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "8080".to_string());
|
||||||
|
let addr = format!("0.0.0.0:{}", port);
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||||
|
|
||||||
|
info!("Charging engine listening on {}", addr);
|
||||||
|
|
||||||
|
axum::serve(listener, app).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreditCheckRequest {
|
||||||
|
bytes_requested: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct CreditCheckResponse {
|
||||||
|
allowed: bool,
|
||||||
|
remaining_bytes: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/credit/:ip/check
|
||||||
|
/// Check if user has enough credit for data usage
|
||||||
|
async fn check_credit(
|
||||||
|
Path(ip): Path<String>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(req): Json<CreditCheckRequest>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Redis connection failed: {}", e);
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(CreditCheckResponse {
|
||||||
|
allowed: false,
|
||||||
|
remaining_bytes: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = format!("credit:{}", ip);
|
||||||
|
|
||||||
|
// Get current credit balance
|
||||||
|
let credit: i64 = conn.get(&key).await.unwrap_or(0);
|
||||||
|
|
||||||
|
let allowed = credit >= req.bytes_requested as i64;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Credit check for {}: {} bytes requested, {} bytes available, allowed: {}",
|
||||||
|
ip, req.bytes_requested, credit, allowed
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(CreditCheckResponse {
|
||||||
|
allowed,
|
||||||
|
remaining_bytes: credit,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DeductRequest {
|
||||||
|
bytes_used: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/credit/:ip/deduct
|
||||||
|
/// Deduct bytes from user's credit balance
|
||||||
|
async fn deduct_credit(
|
||||||
|
Path(ip): Path<String>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(req): Json<DeductRequest>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Redis connection failed: {}", e);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = format!("credit:{}", ip);
|
||||||
|
|
||||||
|
// Atomically subtract bytes
|
||||||
|
let new_balance: i64 = conn.decr(&key, req.bytes_used).await.unwrap_or(0);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"User {} deducted {} bytes, remaining: {}",
|
||||||
|
ip, req.bytes_used, new_balance
|
||||||
|
);
|
||||||
|
|
||||||
|
StatusCode::OK
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AddCreditRequest {
|
||||||
|
bytes_to_add: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/credit/:ip/add
|
||||||
|
/// Add bytes to user's credit balance
|
||||||
|
async fn add_credit(
|
||||||
|
Path(ip): Path<String>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(req): Json<AddCreditRequest>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Redis connection failed: {}", e);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = format!("credit:{}", ip);
|
||||||
|
|
||||||
|
// Atomically add bytes
|
||||||
|
let new_balance: i64 = conn.incr(&key, req.bytes_to_add).await.unwrap_or(0);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"User {} added {} bytes, new balance: {}",
|
||||||
|
ip, req.bytes_to_add, new_balance
|
||||||
|
);
|
||||||
|
|
||||||
|
StatusCode::OK
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct BalanceResponse {
|
||||||
|
ip: String,
|
||||||
|
balance_bytes: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /v1/credit/:ip/balance
|
||||||
|
/// Get current credit balance
|
||||||
|
async fn get_balance(
|
||||||
|
Path(ip): Path<String>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let mut conn = match state.redis.get_multiplexed_async_connection().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => {
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(BalanceResponse {
|
||||||
|
ip: ip.clone(),
|
||||||
|
balance_bytes: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = format!("credit:{}", ip);
|
||||||
|
let balance: i64 = conn.get(&key).await.unwrap_or(0);
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(BalanceResponse {
|
||||||
|
ip,
|
||||||
|
balance_bytes: balance,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn health_check() -> &'static str {
|
||||||
|
"OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "packet-gateway"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
aya = { workspace = true }
|
||||||
|
aya-log = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
redis = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
dotenv = { workspace = true }
|
||||||
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "packet-gateway"
|
||||||
|
path = "src/main.rs"
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
use tokio::time::{interval, Duration};
|
||||||
|
use tracing::{info, warn, error};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Network interface to attach XDP program
|
||||||
|
#[arg(short, long, default_value = "eth0")]
|
||||||
|
interface: String,
|
||||||
|
|
||||||
|
/// Redis connection URL
|
||||||
|
#[arg(short, long, default_value = "redis://127.0.0.1/")]
|
||||||
|
redis_url: String,
|
||||||
|
|
||||||
|
/// Stats sync interval in seconds
|
||||||
|
#[arg(short, long, default_value = "1")]
|
||||||
|
sync_interval: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
// Initialize tracing
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_target(false)
|
||||||
|
.compact()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
info!("Starting Packet Gateway");
|
||||||
|
info!("Interface: {}", args.interface);
|
||||||
|
info!("Redis URL: {}", args.redis_url);
|
||||||
|
|
||||||
|
// NOTE: This is a simplified version
|
||||||
|
// For full eBPF implementation, you need:
|
||||||
|
// 1. Compile eBPF program with aya-bpf
|
||||||
|
// 2. Load and attach XDP program
|
||||||
|
// 3. Access eBPF maps for packet stats and credit control
|
||||||
|
|
||||||
|
// Connect to Redis
|
||||||
|
let redis_client = redis::Client::open(args.redis_url.as_str())?;
|
||||||
|
let mut redis_conn = redis_client.get_multiplexed_async_connection().await?;
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
let _: () = redis::cmd("PING").query_async(&mut redis_conn).await?;
|
||||||
|
info!("Connected to Redis successfully");
|
||||||
|
|
||||||
|
// Simulate packet processing loop
|
||||||
|
// In production, this would read from eBPF maps
|
||||||
|
let mut ticker = interval(Duration::from_secs(args.sync_interval));
|
||||||
|
|
||||||
|
info!("Packet gateway running. Press Ctrl+C to exit.");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
ticker.tick().await;
|
||||||
|
|
||||||
|
// Simulate updating packet statistics to Redis
|
||||||
|
// In real implementation, read from eBPF map
|
||||||
|
let test_ip = "10.0.0.1";
|
||||||
|
let test_bytes: u64 = 1024 * 1024; // 1 MB
|
||||||
|
|
||||||
|
let key = format!("usage:{}", test_ip);
|
||||||
|
let _: () = redis_conn.set_ex(&key, test_bytes, 3600).await?;
|
||||||
|
|
||||||
|
info!("Updated usage for {}: {} bytes", test_ip, test_bytes);
|
||||||
|
|
||||||
|
// Check credit status
|
||||||
|
let credit_key = format!("credit:{}", test_ip);
|
||||||
|
let credit: i64 = redis_conn.get(&credit_key).await.unwrap_or(0);
|
||||||
|
|
||||||
|
if credit <= 0 {
|
||||||
|
warn!("User {} has no credit - would block traffic in eBPF", test_ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<!-- BEGIN:nextjs-agent-rules -->
|
|
||||||
# This is NOT the Next.js you know
|
|
||||||
|
|
||||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
||||||
<!-- END:nextjs-agent-rules -->
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@AGENTS.md
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:7.0
|
||||||
|
container_name: taas-mongodb
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: admin
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: changeme
|
||||||
|
MONGO_INITDB_DATABASE: free5gc
|
||||||
|
volumes:
|
||||||
|
- mongodb_data:/data/db
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:9-alpine
|
||||||
|
container_name: taas-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
api-server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: deployments/docker/api-server.Dockerfile
|
||||||
|
container_name: taas-api-server
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
MONGODB_URI: mongodb://admin:changeme@mongodb:27017/free5gc
|
||||||
|
REDIS_URI: redis://redis:6379
|
||||||
|
API_PORT: 8000
|
||||||
|
GIN_MODE: release
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
carrier-connector:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: deployments/docker/carrier-connector.Dockerfile
|
||||||
|
container_name: taas-carrier-connector
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
SMDP_URL: https://smdp.example.com
|
||||||
|
SMDP_API_KEY: your-api-key
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
charging-engine:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: deployments/docker/charging-engine.Dockerfile
|
||||||
|
container_name: taas-charging-engine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
REDIS_URI: redis://redis:6379
|
||||||
|
SERVER_PORT: 8080
|
||||||
|
RUST_LOG: info
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
web-dashboard:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: deployments/docker/web-dashboard.Dockerfile
|
||||||
|
container_name: taas-web-dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
NEXT_PUBLIC_API_URL: http://api-server:8000
|
||||||
|
depends_on:
|
||||||
|
- api-server
|
||||||
|
networks:
|
||||||
|
- taas-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongodb_data:
|
||||||
|
driver: local
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
taas-network:
|
||||||
|
driver: bridge
|
||||||
+237
@@ -0,0 +1,237 @@
|
|||||||
|
# TaaS Platform API Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The TaaS Platform provides a RESTful API for managing cellular connectivity programmatically. All API endpoints are available at `https://api.taas-platform.com/v1` (production) or `http://localhost:8000/v1` (development).
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
All API requests require authentication using Bearer tokens:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer YOUR_API_KEY" \
|
||||||
|
https://api.taas-platform.com/v1/esims
|
||||||
|
```
|
||||||
|
|
||||||
|
Get your API key from the [Developer Dashboard](http://localhost:3000).
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
**GET** `/health`
|
||||||
|
|
||||||
|
Check API server status.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"service": "api-server",
|
||||||
|
"timestamp": 1711929600
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create eSIM
|
||||||
|
|
||||||
|
**POST** `/v1/esims`
|
||||||
|
|
||||||
|
Provision a new eSIM with cellular connectivity.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data_plan": "1GB",
|
||||||
|
"country_code": "US",
|
||||||
|
"carrier": "optional-carrier-name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"imsi": "208930000000001",
|
||||||
|
"iccid": "8933123456789012345",
|
||||||
|
"activation_code": "LPA:1$smdp.example.com$MATCHING_ID",
|
||||||
|
"status": "provisioned",
|
||||||
|
"data_plan": "1GB",
|
||||||
|
"country_code": "US",
|
||||||
|
"created_at": "2026-04-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get eSIM Details
|
||||||
|
|
||||||
|
**GET** `/v1/esims/:id`
|
||||||
|
|
||||||
|
Retrieve information about a specific eSIM.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"imsi": "208930000000001",
|
||||||
|
"iccid": "8933123456789012345",
|
||||||
|
"status": "active",
|
||||||
|
"data_plan": "1GB",
|
||||||
|
"created_at": "2026-04-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get eSIM Usage
|
||||||
|
|
||||||
|
**GET** `/v1/esims/:id/usage`
|
||||||
|
|
||||||
|
Get data usage statistics for an eSIM.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"data_used": 262144000,
|
||||||
|
"data_limit": 1073741824,
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"started_at": "2026-04-01T10:00:00Z",
|
||||||
|
"ended_at": "2026-04-01T11:00:00Z",
|
||||||
|
"bytes_up": 10485760,
|
||||||
|
"bytes_down": 52428800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated_at": "2026-04-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminate eSIM
|
||||||
|
|
||||||
|
**DELETE** `/v1/esims/:id`
|
||||||
|
|
||||||
|
Permanently terminate an eSIM.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "eSIM terminated successfully",
|
||||||
|
"esim_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status Codes
|
||||||
|
|
||||||
|
- `200 OK` - Request successful
|
||||||
|
- `201 Created` - Resource created
|
||||||
|
- `400 Bad Request` - Invalid request parameters
|
||||||
|
- `401 Unauthorized` - Missing or invalid authentication
|
||||||
|
- `404 Not Found` - Resource not found
|
||||||
|
- `500 Internal Server Error` - Server error
|
||||||
|
|
||||||
|
## Rate Limits
|
||||||
|
|
||||||
|
- **Free Tier**: 100 requests/hour
|
||||||
|
- **Pro Tier**: 10,000 requests/hour
|
||||||
|
- **Enterprise**: Unlimited
|
||||||
|
|
||||||
|
Rate limit headers are included in all responses:
|
||||||
|
```
|
||||||
|
X-RateLimit-Limit: 100
|
||||||
|
X-RateLimit-Remaining: 99
|
||||||
|
X-RateLimit-Reset: 1711933200
|
||||||
|
```
|
||||||
|
|
||||||
|
## SDKs
|
||||||
|
|
||||||
|
### TypeScript/JavaScript
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @taas-platform/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TaasClient } from '@taas-platform/sdk';
|
||||||
|
|
||||||
|
const client = new TaasClient({ apiKey: 'YOUR_API_KEY' });
|
||||||
|
|
||||||
|
// Create eSIM
|
||||||
|
const esim = await client.esims.create({
|
||||||
|
dataPlan: '1GB',
|
||||||
|
countryCode: 'US'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get usage
|
||||||
|
const usage = await client.esims.getUsage(esim.esimId);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/taas-platform/sdk-go
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/taas-platform/sdk-go"
|
||||||
|
|
||||||
|
client := taas.NewClient("YOUR_API_KEY")
|
||||||
|
|
||||||
|
// Create eSIM
|
||||||
|
esim, err := client.ESims.Create(&taas.CreateESIMRequest{
|
||||||
|
DataPlan: "1GB",
|
||||||
|
CountryCode: "US",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Webhooks
|
||||||
|
|
||||||
|
Configure webhooks to receive real-time notifications:
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
- `esim.created` - New eSIM provisioned
|
||||||
|
- `esim.activated` - eSIM activated on device
|
||||||
|
- `usage.threshold` - Data usage threshold reached (50%, 75%, 90%)
|
||||||
|
- `usage.depleted` - Data plan exhausted
|
||||||
|
- `esim.terminated` - eSIM terminated
|
||||||
|
|
||||||
|
### Webhook Payload
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "usage.threshold",
|
||||||
|
"timestamp": "2026-04-01T12:00:00Z",
|
||||||
|
"data": {
|
||||||
|
"esim_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"threshold": 75,
|
||||||
|
"data_used": 805306368,
|
||||||
|
"data_limit": 1073741824
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Create and Activate eSIM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create eSIM
|
||||||
|
curl -X POST https://api.taas-platform.com/v1/esims \
|
||||||
|
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"data_plan": "1GB",
|
||||||
|
"country_code": "US"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 2. Show QR code to user (activation_code)
|
||||||
|
# User scans QR code on their device
|
||||||
|
|
||||||
|
# 3. Monitor usage
|
||||||
|
curl https://api.taas-platform.com/v1/esims/ESIM_ID/usage \
|
||||||
|
-H "Authorization: Bearer YOUR_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Documentation**: https://docs.taas-platform.com
|
||||||
|
- **API Status**: https://status.taas-platform.com
|
||||||
|
- **Support Email**: support@taas-platform.com
|
||||||
|
- **GitHub Issues**: https://github.com/nutcas3/telecom-platform/issues
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
# TaaS Platform Architecture
|
||||||
|
|
||||||
|
## System Overview
|
||||||
|
|
||||||
|
The TaaS Platform is a multi-layer telecom infrastructure that provides cellular connectivity as a service. It implements 3GPP standards while exposing a modern, developer-friendly API.
|
||||||
|
|
||||||
|
## Architecture Layers
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Developer Interface │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ Web UI │ │ TypeScript │ │ REST API │ │
|
||||||
|
│ │ Dashboard │ │ SDK │ │ Webhooks │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Business Support System (BSS) │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ API Server │ │ Carrier │ │ Subscriber │ │
|
||||||
|
│ │ (Go 1.26) │ │ Connector │ │ Management │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Operations Support System (OSS) │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ Charging │ │ Usage │ │ eSIM │ │
|
||||||
|
│ │ Engine │ │ Tracking │ │ Provisioning│ │
|
||||||
|
│ │ (Rust) │ │ (Redis) │ │ (ES2+) │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Telecom Core Network │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ 5G Core │ │ Packet │ │ Data Plane │ │
|
||||||
|
│ │ (free5GC) │ │ Gateway │ │ (eBPF) │ │
|
||||||
|
│ │ (Go) │ │ (Rust) │ │ │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Details
|
||||||
|
|
||||||
|
### 1. API Server (Go 1.26)
|
||||||
|
|
||||||
|
**Purpose**: Developer-facing RESTful API and business logic
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Language: Go 1.26 (Green Tea GC, new(expr) syntax)
|
||||||
|
- Framework: Gin
|
||||||
|
- Database: MongoDB (subscriber data)
|
||||||
|
- Cache: Redis (session management)
|
||||||
|
|
||||||
|
**Responsibilities**:
|
||||||
|
- eSIM creation and management
|
||||||
|
- Authentication and authorization
|
||||||
|
- Rate limiting and quotas
|
||||||
|
- Webhook delivery
|
||||||
|
- API documentation
|
||||||
|
|
||||||
|
**Endpoints**:
|
||||||
|
- `POST /v1/esims` - Create eSIM
|
||||||
|
- `GET /v1/esims/:id` - Get eSIM details
|
||||||
|
- `GET /v1/esims/:id/usage` - Get usage stats
|
||||||
|
- `DELETE /v1/esims/:id` - Terminate eSIM
|
||||||
|
|
||||||
|
### 2. Carrier Connector (Go 1.26)
|
||||||
|
|
||||||
|
**Purpose**: Integration with GSMA SM-DP+ servers for eSIM provisioning
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Language: Go 1.26
|
||||||
|
- Protocol: HTTPS (ES2+ API)
|
||||||
|
- Vendor: Thales, G+D, or Idemia
|
||||||
|
|
||||||
|
**Responsibilities**:
|
||||||
|
- Profile ordering via ES2+ protocol
|
||||||
|
- Activation code generation (LPA strings)
|
||||||
|
- Profile status tracking
|
||||||
|
- Vendor-specific adaptations
|
||||||
|
|
||||||
|
**Flow**:
|
||||||
|
1. Receive eSIM order from API Server
|
||||||
|
2. Call SM-DP+ vendor API with IMSI, K, OPc keys
|
||||||
|
3. Receive activation code (LPA:1$...)
|
||||||
|
4. Return to API Server for user display
|
||||||
|
|
||||||
|
### 3. Charging Engine (Rust 1.94)
|
||||||
|
|
||||||
|
**Purpose**: Real-time credit control and billing
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Language: Rust 1.94 (array_windows, async Tokio)
|
||||||
|
- Framework: Axum
|
||||||
|
- Database: Redis (in-memory credit balances)
|
||||||
|
|
||||||
|
**Responsibilities**:
|
||||||
|
- Credit balance management
|
||||||
|
- Real-time authorization checks
|
||||||
|
- Usage deduction (atomic operations)
|
||||||
|
- Credit top-up processing
|
||||||
|
|
||||||
|
**API**:
|
||||||
|
- `POST /v1/credit/:ip/check` - Check if user has credit
|
||||||
|
- `POST /v1/credit/:ip/deduct` - Deduct bytes used
|
||||||
|
- `POST /v1/credit/:ip/add` - Add credit
|
||||||
|
- `GET /v1/credit/:ip/balance` - Get balance
|
||||||
|
|
||||||
|
**Performance**:
|
||||||
|
- Sub-millisecond response times
|
||||||
|
- Atomic Redis operations (INCR, DECR)
|
||||||
|
- Horizontal scaling via Redis Cluster
|
||||||
|
|
||||||
|
### 4. Packet Gateway (Rust 1.94 + eBPF)
|
||||||
|
|
||||||
|
**Purpose**: High-performance packet processing and traffic control
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Language: Rust 1.94
|
||||||
|
- Framework: Aya (pure Rust eBPF)
|
||||||
|
- Attach Point: XDP (eXpress Data Path)
|
||||||
|
- Protocol: GTP-U decapsulation (UDP port 2152)
|
||||||
|
|
||||||
|
**Responsibilities**:
|
||||||
|
- Parse GTP-U tunneled packets
|
||||||
|
- Extract user IP addresses
|
||||||
|
- Count bytes per subscriber
|
||||||
|
- Block traffic for users without credit
|
||||||
|
- Sync stats to Redis
|
||||||
|
|
||||||
|
**Architecture**:
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Network Interface (eth0) │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────▼─────────┐
|
||||||
|
│ XDP Hook │ ← eBPF program runs here
|
||||||
|
│ (Kernel) │
|
||||||
|
└─────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌─────────▼─────────┐
|
||||||
|
│ eBPF Maps │
|
||||||
|
│ - Packet Stats │
|
||||||
|
│ - Credit Status │
|
||||||
|
└─────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌─────────▼─────────┐
|
||||||
|
│ Userspace Daemon │ ← Rust application
|
||||||
|
│ (Reads maps, │
|
||||||
|
│ syncs to Redis) │
|
||||||
|
└───────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Packet Processing**:
|
||||||
|
1. Packet arrives at NIC
|
||||||
|
2. XDP hook inspects Ethernet → IP → UDP → GTP-U headers
|
||||||
|
3. Extract inner IP packet (actual user data)
|
||||||
|
4. Look up credit status in eBPF map
|
||||||
|
5. If credit > 0: `XDP_PASS`, else: `XDP_DROP`
|
||||||
|
6. Update packet statistics in eBPF map
|
||||||
|
7. Userspace daemon reads stats, syncs to Redis
|
||||||
|
|
||||||
|
### 5. 5G Core Network (free5GC)
|
||||||
|
|
||||||
|
**Purpose**: 3GPP-compliant 5G standalone core
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Project: free5GC v4.2.1
|
||||||
|
- Language: Go
|
||||||
|
- Database: MongoDB
|
||||||
|
- Deployment: Kubernetes or bare metal
|
||||||
|
|
||||||
|
**Network Functions**:
|
||||||
|
- **AMF** (Access and Mobility Management)
|
||||||
|
- **SMF** (Session Management)
|
||||||
|
- **UPF** (User Plane Function)
|
||||||
|
- **PCF** (Policy Control)
|
||||||
|
- **UDM** (Unified Data Management)
|
||||||
|
- **UDR** (Unified Data Repository)
|
||||||
|
- **AUSF** (Authentication Server)
|
||||||
|
- **NRF** (NF Repository)
|
||||||
|
|
||||||
|
**Integration Points**:
|
||||||
|
- API Server writes subscriber data to MongoDB
|
||||||
|
- UPF forwards GTP-U traffic to Packet Gateway
|
||||||
|
- AMF handles UE authentication using stored keys (K, OPc)
|
||||||
|
|
||||||
|
### 6. Web Dashboard (Next.js 15)
|
||||||
|
|
||||||
|
**Purpose**: Developer portal and management interface
|
||||||
|
|
||||||
|
**Technology**:
|
||||||
|
- Framework: Next.js 15 (App Router)
|
||||||
|
- Language: TypeScript
|
||||||
|
- Styling: Tailwind CSS
|
||||||
|
- Data Fetching: TanStack Query
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- eSIM creation and management
|
||||||
|
- Real-time usage charts (Recharts)
|
||||||
|
- API key management
|
||||||
|
- Webhook configuration
|
||||||
|
- Billing and invoices
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
### eSIM Creation Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Developer → API Server
|
||||||
|
POST /v1/esims { data_plan: "1GB" }
|
||||||
|
|
||||||
|
2. API Server → IMSI Allocator
|
||||||
|
Generate unique 15-digit IMSI
|
||||||
|
|
||||||
|
3. API Server → Key Generator
|
||||||
|
Generate K (subscriber key)
|
||||||
|
Generate OPc (operator key)
|
||||||
|
|
||||||
|
4. API Server → MongoDB (free5GC)
|
||||||
|
Insert subscriber data:
|
||||||
|
- subscriptionData.authenticationData (K, OPc)
|
||||||
|
- subscriptionData.provisionedData (IMSI, AMBR)
|
||||||
|
|
||||||
|
5. API Server → Carrier Connector
|
||||||
|
Order eSIM profile via ES2+
|
||||||
|
|
||||||
|
6. Carrier Connector → SM-DP+ Vendor
|
||||||
|
HTTPS POST with IMSI, K, OPc
|
||||||
|
|
||||||
|
7. SM-DP+ → Carrier Connector
|
||||||
|
Return activation code (LPA string)
|
||||||
|
|
||||||
|
8. API Server → Developer
|
||||||
|
Return eSIM details + activation code
|
||||||
|
|
||||||
|
9. Developer → End User
|
||||||
|
Display QR code with LPA string
|
||||||
|
|
||||||
|
10. End User's Device → SM-DP+
|
||||||
|
Scan QR, download eSIM profile
|
||||||
|
|
||||||
|
11. Device → free5GC AMF
|
||||||
|
Registration procedure (5G AKA)
|
||||||
|
|
||||||
|
12. free5GC → Packet Gateway
|
||||||
|
Establish GTP-U tunnel for data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Real-Time Usage Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User Device sends data
|
||||||
|
↓
|
||||||
|
2. free5GC UPF encapsulates in GTP-U
|
||||||
|
↓
|
||||||
|
3. Packet Gateway (eBPF XDP)
|
||||||
|
- Decapsulate GTP-U
|
||||||
|
- Extract user IP
|
||||||
|
- Check credit status in eBPF map
|
||||||
|
↓
|
||||||
|
4. If credit > 0:
|
||||||
|
- Pass packet (XDP_PASS)
|
||||||
|
- Increment byte counter in eBPF map
|
||||||
|
↓
|
||||||
|
5. Userspace daemon (Rust)
|
||||||
|
- Reads eBPF maps every 1 second
|
||||||
|
- Writes stats to Redis
|
||||||
|
↓
|
||||||
|
6. Charging Engine
|
||||||
|
- Reads usage from Redis
|
||||||
|
- Deducts from credit balance
|
||||||
|
- Updates credit status
|
||||||
|
↓
|
||||||
|
7. Packet Gateway
|
||||||
|
- Reads new credit status from Redis
|
||||||
|
- Updates eBPF map
|
||||||
|
↓
|
||||||
|
8. If credit reaches 0:
|
||||||
|
- eBPF drops packets (XDP_DROP)
|
||||||
|
- User's internet stops
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scaling Architecture
|
||||||
|
|
||||||
|
### Horizontal Scaling
|
||||||
|
|
||||||
|
**Stateless Components** (can be replicated):
|
||||||
|
- API Server: 3+ replicas behind load balancer
|
||||||
|
- Carrier Connector: 2+ replicas
|
||||||
|
- Charging Engine: 2+ replicas
|
||||||
|
- Web Dashboard: CDN + multiple origins
|
||||||
|
|
||||||
|
**Stateful Components** (require special handling):
|
||||||
|
- MongoDB: Replica set (1 primary, 2 secondaries)
|
||||||
|
- Redis: Cluster mode or Sentinel
|
||||||
|
- Packet Gateway: One instance per physical server (eBPF)
|
||||||
|
- free5GC: Kubernetes deployment with dedicated UPF nodes
|
||||||
|
|
||||||
|
### Performance Metrics
|
||||||
|
|
||||||
|
**Throughput**:
|
||||||
|
- API Server: 10,000 requests/second
|
||||||
|
- Charging Engine: 50,000 credit checks/second
|
||||||
|
- Packet Gateway: 10 Gbps+ per server (eBPF)
|
||||||
|
- free5GC UPF: 5 Gbps per instance
|
||||||
|
|
||||||
|
**Latency**:
|
||||||
|
- API Server: <50ms p99
|
||||||
|
- Charging Engine: <1ms p99 (Redis)
|
||||||
|
- Packet Gateway: <1µs (eBPF in kernel)
|
||||||
|
- eSIM Provisioning: <2 seconds end-to-end
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Authentication & Authorization
|
||||||
|
- API keys with HMAC signatures
|
||||||
|
- JWT tokens for web dashboard
|
||||||
|
- OAuth 2.0 for third-party integrations
|
||||||
|
- Rate limiting per API key
|
||||||
|
|
||||||
|
### Data Encryption
|
||||||
|
- TLS 1.3 for all API endpoints
|
||||||
|
- MongoDB encryption at rest
|
||||||
|
- Redis TLS connections
|
||||||
|
- eBPF packet inspection (no decryption)
|
||||||
|
|
||||||
|
### Secrets Management
|
||||||
|
- Kubernetes Secrets for production
|
||||||
|
- HashiCorp Vault for sensitive keys
|
||||||
|
- Environment variables for local dev
|
||||||
|
- No hardcoded credentials
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
make all # Build all components
|
||||||
|
make db-setup # Set up MongoDB + Redis
|
||||||
|
make dev # Start all services
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployments/kubernetes/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Checklist
|
||||||
|
- [ ] MongoDB replica set configured
|
||||||
|
- [ ] Redis Cluster or Sentinel enabled
|
||||||
|
- [ ] TLS certificates installed
|
||||||
|
- [ ] Monitoring (Prometheus, Grafana)
|
||||||
|
- [ ] Logging (ELK Stack or Loki)
|
||||||
|
- [ ] Backup strategy implemented
|
||||||
|
- [ ] Disaster recovery plan documented
|
||||||
|
- [ ] Load testing completed (10x expected traffic)
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Metrics (Prometheus)
|
||||||
|
- `api_requests_total` - Total API requests
|
||||||
|
- `esim_creations_total` - eSIMs created
|
||||||
|
- `data_usage_bytes_total` - Total data usage
|
||||||
|
- `charging_checks_duration_seconds` - Credit check latency
|
||||||
|
- `packet_gateway_packets_total` - Packets processed
|
||||||
|
|
||||||
|
### Alerts
|
||||||
|
- High error rate (>1%)
|
||||||
|
- Low credit balances (<10% of users)
|
||||||
|
- MongoDB replica lag (>10 seconds)
|
||||||
|
- Redis memory usage (>90%)
|
||||||
|
- Packet gateway packet loss (>0.1%)
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
1. **Multi-Region Deployment**
|
||||||
|
- Deploy in multiple AWS regions
|
||||||
|
- Global load balancing with Route 53
|
||||||
|
- Data residency compliance
|
||||||
|
|
||||||
|
2. **Advanced Analytics**
|
||||||
|
- Real-time dashboards with WebSockets
|
||||||
|
- Predictive usage forecasting
|
||||||
|
- Anomaly detection (unusual data patterns)
|
||||||
|
|
||||||
|
3. **Additional Carriers**
|
||||||
|
- Support for multiple SM-DP+ vendors
|
||||||
|
- Dynamic carrier selection based on region
|
||||||
|
- Fallback mechanisms
|
||||||
|
|
||||||
|
4. **IoT Optimization**
|
||||||
|
- SGP.32 support (IoT eSIM standard)
|
||||||
|
- LPWAN protocol support (NB-IoT, LTE-M)
|
||||||
|
- Power-saving features
|
||||||
|
|
||||||
|
5. **Developer Tools**
|
||||||
|
- CLI tool for API interaction
|
||||||
|
- Terraform provider
|
||||||
|
- GitHub Actions integration
|
||||||
@@ -0,0 +1,454 @@
|
|||||||
|
# Deployment Guide
|
||||||
|
|
||||||
|
This guide covers deploying the TaaS Platform in different environments.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Local Development](#local-development)
|
||||||
|
2. [Docker Deployment](#docker-deployment)
|
||||||
|
3. [Kubernetes Deployment](#kubernetes-deployment)
|
||||||
|
4. [Production Considerations](#production-considerations)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.26+
|
||||||
|
- Rust 1.94+
|
||||||
|
- Node.js 22+
|
||||||
|
- MongoDB 7.0+
|
||||||
|
- Redis 7+
|
||||||
|
- pnpm
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone repository
|
||||||
|
git clone https://github.com/nutcas3/telecom-platform.git
|
||||||
|
cd telecom-platform
|
||||||
|
|
||||||
|
# 2. Run setup script
|
||||||
|
chmod +x scripts/dev-setup.sh
|
||||||
|
./scripts/dev-setup.sh
|
||||||
|
|
||||||
|
# 3. Set up databases
|
||||||
|
make db-setup
|
||||||
|
|
||||||
|
# 4. Build all components
|
||||||
|
make all
|
||||||
|
|
||||||
|
# 5. Start services (in separate terminals)
|
||||||
|
# Terminal 1: API Server
|
||||||
|
./dist/api-server
|
||||||
|
|
||||||
|
# Terminal 2: Carrier Connector
|
||||||
|
./dist/carrier-connector
|
||||||
|
|
||||||
|
# Terminal 3: Charging Engine
|
||||||
|
./target/release/charging-engine
|
||||||
|
|
||||||
|
# Terminal 4: Web Dashboard
|
||||||
|
cd apps/web-dashboard && pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Create `.env` files in each service directory:
|
||||||
|
|
||||||
|
**apps/api-server/.env:**
|
||||||
|
```env
|
||||||
|
MONGODB_URI=mongodb://localhost:27017/free5gc
|
||||||
|
REDIS_URI=redis://localhost:6379
|
||||||
|
API_PORT=8000
|
||||||
|
GIN_MODE=debug
|
||||||
|
```
|
||||||
|
|
||||||
|
**apps/charging-engine/.env:**
|
||||||
|
```env
|
||||||
|
REDIS_URI=redis://127.0.0.1/
|
||||||
|
SERVER_PORT=8080
|
||||||
|
RUST_LOG=info
|
||||||
|
```
|
||||||
|
|
||||||
|
**apps/web-dashboard/.env.local:**
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Deployment
|
||||||
|
|
||||||
|
### Build Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build all Docker images
|
||||||
|
make docker-build
|
||||||
|
|
||||||
|
# Or build individually
|
||||||
|
docker build -f deployments/docker/api-server.Dockerfile -t taas-api-server .
|
||||||
|
docker build -f deployments/docker/charging-engine.Dockerfile -t taas-charging-engine .
|
||||||
|
docker build -f deployments/docker/web-dashboard.Dockerfile -t taas-web-dashboard .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Stop and remove volumes
|
||||||
|
docker-compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service URLs
|
||||||
|
|
||||||
|
After starting with docker-compose:
|
||||||
|
|
||||||
|
- **API Server**: http://localhost:8000
|
||||||
|
- **Charging Engine**: http://localhost:8080
|
||||||
|
- **Web Dashboard**: http://localhost:3000
|
||||||
|
- **MongoDB**: mongodb://localhost:27017
|
||||||
|
- **Redis**: redis://localhost:6379
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
**MongoDB connection issues:**
|
||||||
|
```bash
|
||||||
|
# Check if MongoDB is running
|
||||||
|
docker-compose ps mongodb
|
||||||
|
|
||||||
|
# View MongoDB logs
|
||||||
|
docker-compose logs mongodb
|
||||||
|
|
||||||
|
# Connect to MongoDB shell
|
||||||
|
docker exec -it taas-mongodb mongosh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis connection issues:**
|
||||||
|
```bash
|
||||||
|
# Test Redis connection
|
||||||
|
docker exec -it taas-redis redis-cli ping
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kubernetes Deployment
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes cluster (v1.28+)
|
||||||
|
- kubectl configured
|
||||||
|
- Persistent storage provisioner
|
||||||
|
- LoadBalancer service support (or Ingress controller)
|
||||||
|
|
||||||
|
### Deploy to Kubernetes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create namespace
|
||||||
|
kubectl create namespace taas-platform
|
||||||
|
|
||||||
|
# 2. Deploy all manifests
|
||||||
|
kubectl apply -f deployments/kubernetes/
|
||||||
|
|
||||||
|
# 3. Verify deployments
|
||||||
|
kubectl get pods -n taas-platform
|
||||||
|
|
||||||
|
# Expected output:
|
||||||
|
# NAME READY STATUS RESTARTS AGE
|
||||||
|
# mongodb-xxx 1/1 Running 0 2m
|
||||||
|
# redis-xxx 1/1 Running 0 2m
|
||||||
|
# api-server-xxx 1/1 Running 0 1m
|
||||||
|
# charging-engine-xxx 1/1 Running 0 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get API Server external IP
|
||||||
|
kubectl get svc api-server -n taas-platform
|
||||||
|
|
||||||
|
# Port forward for local access
|
||||||
|
kubectl port-forward -n taas-platform svc/api-server 8000:8000
|
||||||
|
kubectl port-forward -n taas-platform svc/charging-engine 8080:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scaling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scale API Server
|
||||||
|
kubectl scale deployment api-server -n taas-platform --replicas=5
|
||||||
|
|
||||||
|
# Scale Charging Engine
|
||||||
|
kubectl scale deployment charging-engine -n taas-platform --replicas=3
|
||||||
|
|
||||||
|
# Enable autoscaling
|
||||||
|
kubectl autoscale deployment api-server -n taas-platform \
|
||||||
|
--min=3 --max=10 --cpu-percent=70
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View logs
|
||||||
|
kubectl logs -f deployment/api-server -n taas-platform
|
||||||
|
|
||||||
|
# Describe pod for issues
|
||||||
|
kubectl describe pod <pod-name> -n taas-platform
|
||||||
|
|
||||||
|
# Get events
|
||||||
|
kubectl get events -n taas-platform --sort-by='.lastTimestamp'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update image
|
||||||
|
kubectl set image deployment/api-server \
|
||||||
|
api-server=taas-api-server:v2.0.0 \
|
||||||
|
-n taas-platform
|
||||||
|
|
||||||
|
# Check rollout status
|
||||||
|
kubectl rollout status deployment/api-server -n taas-platform
|
||||||
|
|
||||||
|
# Rollback if needed
|
||||||
|
kubectl rollout undo deployment/api-server -n taas-platform
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Considerations
|
||||||
|
|
||||||
|
### High Availability
|
||||||
|
|
||||||
|
**MongoDB Replica Set:**
|
||||||
|
```yaml
|
||||||
|
# Use MongoDB Operator or StatefulSet
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: mongodb
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
serviceName: mongodb
|
||||||
|
# ... (see MongoDB Operator docs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis Cluster:**
|
||||||
|
```bash
|
||||||
|
# Deploy Redis in cluster mode
|
||||||
|
helm install redis bitnami/redis-cluster \
|
||||||
|
--namespace taas-platform \
|
||||||
|
--set cluster.nodes=6 \
|
||||||
|
--set cluster.replicas=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
**Secrets Management:**
|
||||||
|
```bash
|
||||||
|
# Create Kubernetes secrets
|
||||||
|
kubectl create secret generic mongodb-secret \
|
||||||
|
--from-literal=username=admin \
|
||||||
|
--from-literal=password='STRONG_PASSWORD' \
|
||||||
|
-n taas-platform
|
||||||
|
|
||||||
|
kubectl create secret generic api-keys \
|
||||||
|
--from-literal=jwt-secret='JWT_SECRET' \
|
||||||
|
-n taas-platform
|
||||||
|
```
|
||||||
|
|
||||||
|
**Network Policies:**
|
||||||
|
```yaml
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: api-server-policy
|
||||||
|
namespace: taas-platform
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: api-server
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: web-dashboard
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8000
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: mongodb
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 27017
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring Stack
|
||||||
|
|
||||||
|
**Prometheus & Grafana:**
|
||||||
|
```bash
|
||||||
|
# Install using Helm
|
||||||
|
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||||
|
helm install prometheus prometheus-community/kube-prometheus-stack \
|
||||||
|
-n monitoring --create-namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
**Application Metrics:**
|
||||||
|
- Add `/metrics` endpoint to each service
|
||||||
|
- Use Prometheus client libraries
|
||||||
|
- Create Grafana dashboards
|
||||||
|
|
||||||
|
### Backup Strategy
|
||||||
|
|
||||||
|
**MongoDB Backups:**
|
||||||
|
```bash
|
||||||
|
# Daily backups with mongodump
|
||||||
|
kubectl create cronjob mongodb-backup \
|
||||||
|
--image=mongo:7.0 \
|
||||||
|
--schedule="0 2 * * *" \
|
||||||
|
-- mongodump --uri=$MONGODB_URI --out=/backup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis Persistence:**
|
||||||
|
```bash
|
||||||
|
# Enable AOF in Redis config
|
||||||
|
kubectl edit configmap redis-config -n taas-platform
|
||||||
|
# Set: appendonly yes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resource Limits
|
||||||
|
|
||||||
|
**Recommended limits per service:**
|
||||||
|
|
||||||
|
| Service | CPU Request | CPU Limit | Memory Request | Memory Limit |
|
||||||
|
|---------|-------------|-----------|----------------|--------------|
|
||||||
|
| API Server | 100m | 500m | 128Mi | 512Mi |
|
||||||
|
| Charging Engine | 100m | 1000m | 128Mi | 512Mi |
|
||||||
|
| Packet Gateway | 250m | 2000m | 256Mi | 1Gi |
|
||||||
|
| MongoDB | 250m | 1000m | 512Mi | 2Gi |
|
||||||
|
| Redis | 100m | 500m | 256Mi | 2Gi |
|
||||||
|
|
||||||
|
### Load Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install k6
|
||||||
|
brew install k6 # macOS
|
||||||
|
# or: sudo apt-get install k6
|
||||||
|
|
||||||
|
# Run load test
|
||||||
|
k6 run scripts/loadtest.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sample load test script:**
|
||||||
|
```javascript
|
||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '2m', target: 100 }, // Ramp up
|
||||||
|
{ duration: '5m', target: 100 }, // Stay at 100 users
|
||||||
|
{ duration: '2m', target: 200 }, // Ramp to 200
|
||||||
|
{ duration: '5m', target: 200 }, // Stay at 200
|
||||||
|
{ duration: '2m', target: 0 }, // Ramp down
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const res = http.get('http://api-server:8000/health');
|
||||||
|
check(res, { 'status is 200': (r) => r.status === 200 });
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disaster Recovery
|
||||||
|
|
||||||
|
**Backup Locations:**
|
||||||
|
- MongoDB backups: S3 bucket (cross-region replication)
|
||||||
|
- Redis RDB snapshots: S3 bucket
|
||||||
|
- Configuration: Git repository
|
||||||
|
- Secrets: HashiCorp Vault or AWS Secrets Manager
|
||||||
|
|
||||||
|
**Recovery Time Objective (RTO):** <1 hour
|
||||||
|
**Recovery Point Objective (RPO):** <15 minutes
|
||||||
|
|
||||||
|
**Recovery Procedure:**
|
||||||
|
1. Spin up new Kubernetes cluster
|
||||||
|
2. Restore MongoDB from latest backup
|
||||||
|
3. Restore Redis from snapshot
|
||||||
|
4. Deploy all services from Git
|
||||||
|
5. Update DNS to point to new cluster
|
||||||
|
6. Verify functionality
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
|
||||||
|
**GitHub Actions example:**
|
||||||
|
```yaml
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build Docker images
|
||||||
|
run: make docker-build
|
||||||
|
|
||||||
|
- name: Push to registry
|
||||||
|
run: |
|
||||||
|
docker tag taas-api-server registry.example.com/taas-api-server:${{ github.sha }}
|
||||||
|
docker push registry.example.com/taas-api-server:${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: |
|
||||||
|
kubectl set image deployment/api-server \
|
||||||
|
api-server=registry.example.com/taas-api-server:${{ github.sha }} \
|
||||||
|
-n taas-platform
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cost Optimization
|
||||||
|
|
||||||
|
**Cloud Provider Recommendations:**
|
||||||
|
- Use spot/preemptible instances for non-critical services
|
||||||
|
- Enable autoscaling to match load
|
||||||
|
- Use reserved instances for predictable workloads
|
||||||
|
- Store backups in cheaper storage classes (S3 Glacier)
|
||||||
|
|
||||||
|
**Estimated Monthly Costs (AWS):**
|
||||||
|
- 3x t3.medium (API Server): $90
|
||||||
|
- 2x t3.medium (Charging Engine): $60
|
||||||
|
- 1x t3.large (MongoDB): $75
|
||||||
|
- 1x t3.small (Redis): $19
|
||||||
|
- Load Balancer: $25
|
||||||
|
- Data transfer: Variable
|
||||||
|
- **Total**: ~$270/month (base infrastructure)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For deployment issues:
|
||||||
|
- Check logs: `kubectl logs -f <pod-name>`
|
||||||
|
- Review events: `kubectl get events`
|
||||||
|
- Join community: [Discussions](https://github.com/taas-platform/discussions)
|
||||||
|
- Email: devops@taas-platform.com
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "telecom-platform",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Sovereign Telecom-as-a-Service Platform",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "pnpm --filter web-dashboard dev",
|
||||||
|
"build": "pnpm -r run build",
|
||||||
|
"lint": "pnpm -r run lint",
|
||||||
|
"test": "pnpm -r run test",
|
||||||
|
"clean": "pnpm -r exec rm -rf .next dist node_modules"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"telecom",
|
||||||
|
"5g",
|
||||||
|
"esim",
|
||||||
|
"taas",
|
||||||
|
"cellular",
|
||||||
|
"connectivity"
|
||||||
|
],
|
||||||
|
"author": "TaaS Team",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.0.0",
|
||||||
|
"pnpm": ">=9.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "[email protected]"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
packages:
|
||||||
|
- 'apps/web-dashboard'
|
||||||
|
- 'libs/shared-ts-sdk'
|
||||||
Executable
+144
@@ -0,0 +1,144 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "TaaS Platform - Development Setup"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to check command exists
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Go 1.26
|
||||||
|
echo -n "Checking Go 1.26... "
|
||||||
|
if command_exists go; then
|
||||||
|
GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//')
|
||||||
|
if [[ "$GO_VERSION" == 1.26* ]]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found $(go version)"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} Found Go $GO_VERSION (1.26 recommended)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Not found"
|
||||||
|
echo "Please install Go 1.26: https://go.dev/dl/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Rust 1.94
|
||||||
|
echo -n "Checking Rust 1.94... "
|
||||||
|
if command_exists rustc; then
|
||||||
|
RUST_VERSION=$(rustc --version | awk '{print $2}')
|
||||||
|
if [[ "$RUST_VERSION" == 1.94* ]]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found $(rustc --version)"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} Found Rust $RUST_VERSION (1.94 recommended)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Not found"
|
||||||
|
echo "Please install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Node.js 22
|
||||||
|
echo -n "Checking Node.js 22... "
|
||||||
|
if command_exists node; then
|
||||||
|
NODE_VERSION=$(node --version | sed 's/v//')
|
||||||
|
if [[ "$NODE_VERSION" == 22* ]]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found $(node --version)"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} Found Node.js $NODE_VERSION (22 recommended)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Not found"
|
||||||
|
echo "Please install Node.js 22: https://nodejs.org/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check pnpm
|
||||||
|
echo -n "Checking pnpm... "
|
||||||
|
if command_exists pnpm; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found $(pnpm --version)"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Not found"
|
||||||
|
echo "Installing pnpm..."
|
||||||
|
npm install -g pnpm
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check MongoDB
|
||||||
|
echo -n "Checking MongoDB... "
|
||||||
|
if command_exists mongosh; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} MongoDB shell not found"
|
||||||
|
echo "Please install MongoDB: https://www.mongodb.com/docs/manual/installation/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Redis
|
||||||
|
echo -n "Checking Redis... "
|
||||||
|
if command_exists redis-cli; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} Redis not found"
|
||||||
|
echo "Install Redis: sudo apt-get install redis-server"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Docker
|
||||||
|
echo -n "Checking Docker... "
|
||||||
|
if command_exists docker; then
|
||||||
|
echo -e "${GREEN}✓${NC} Found $(docker --version | cut -d ' ' -f3)"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠${NC} Docker not found (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "Installing Dependencies"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Install Go dependencies
|
||||||
|
echo "Installing Go dependencies..."
|
||||||
|
cd apps/api-server && go mod download && cd ../..
|
||||||
|
cd apps/carrier-connector && go mod download && cd ../..
|
||||||
|
|
||||||
|
# Install Rust dependencies
|
||||||
|
echo "Installing Rust dependencies..."
|
||||||
|
cargo fetch
|
||||||
|
|
||||||
|
# Install Node.js dependencies
|
||||||
|
echo "Installing Node.js dependencies..."
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "Creating Environment Files"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create .env files from examples
|
||||||
|
for env_example in $(find . -name ".env.example"); do
|
||||||
|
env_file="${env_example%.example}"
|
||||||
|
if [ ! -f "$env_file" ]; then
|
||||||
|
cp "$env_example" "$env_file"
|
||||||
|
echo "Created $env_file"
|
||||||
|
else
|
||||||
|
echo "Skipped $env_file (already exists)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "Setup Complete!"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Set up databases: make db-setup"
|
||||||
|
echo "2. Build all components: make all"
|
||||||
|
echo "3. Start development: make dev"
|
||||||
|
echo ""
|
||||||
|
echo "For help: make help"
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Setting up MongoDB for TaaS Platform..."
|
||||||
|
|
||||||
|
# Check if MongoDB is installed
|
||||||
|
if ! command -v mongosh &> /dev/null; then
|
||||||
|
echo "MongoDB shell (mongosh) not found. Please install MongoDB first."
|
||||||
|
echo "Visit: https://www.mongodb.com/docs/manual/installation/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# MongoDB connection details
|
||||||
|
MONGO_HOST=${MONGO_HOST:-"localhost"}
|
||||||
|
MONGO_PORT=${MONGO_PORT:-"27017"}
|
||||||
|
MONGO_USER=${MONGO_USER:-"free5gc"}
|
||||||
|
MONGO_PASS=${MONGO_PASS:-"free5gc_password"}
|
||||||
|
MONGO_DB="free5gc"
|
||||||
|
|
||||||
|
echo "Creating MongoDB database and user..."
|
||||||
|
|
||||||
|
# Create database and user
|
||||||
|
mongosh --host $MONGO_HOST --port $MONGO_PORT <<EOF
|
||||||
|
use admin
|
||||||
|
|
||||||
|
// Create user if not exists
|
||||||
|
if (!db.getUser("$MONGO_USER")) {
|
||||||
|
db.createUser({
|
||||||
|
user: "$MONGO_USER",
|
||||||
|
pwd: "$MONGO_PASS",
|
||||||
|
roles: [
|
||||||
|
{ role: "readWrite", db: "$MONGO_DB" },
|
||||||
|
{ role: "dbAdmin", db: "$MONGO_DB" }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
print("User $MONGO_USER created successfully")
|
||||||
|
} else {
|
||||||
|
print("User $MONGO_USER already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
use $MONGO_DB
|
||||||
|
|
||||||
|
// Create collections for free5GC
|
||||||
|
db.createCollection("subscriptionData.provisionedData.amData")
|
||||||
|
db.createCollection("subscriptionData.authenticationData.authenticationSubscription")
|
||||||
|
db.createCollection("subscriptionData.provisionedData.smData")
|
||||||
|
db.createCollection("policyData.ues.amData")
|
||||||
|
db.createCollection("policyData.ues.smData")
|
||||||
|
|
||||||
|
print("Collections created successfully")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "MongoDB setup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Connection string: mongodb://$MONGO_USER:$MONGO_PASS@$MONGO_HOST:$MONGO_PORT/$MONGO_DB"
|
||||||
|
echo ""
|
||||||
|
echo "Update your .env files with:"
|
||||||
|
echo "MONGODB_URI=mongodb://$MONGO_USER:$MONGO_PASS@$MONGO_HOST:$MONGO_PORT/$MONGO_DB"
|
||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Setting up Redis for TaaS Platform..."
|
||||||
|
|
||||||
|
# Check if Redis is installed
|
||||||
|
if ! command -v redis-cli &> /dev/null; then
|
||||||
|
echo "Redis CLI not found. Please install Redis first."
|
||||||
|
echo "Ubuntu/Debian: sudo apt-get install redis-server"
|
||||||
|
echo "macOS: brew install redis"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Redis connection details
|
||||||
|
REDIS_HOST=${REDIS_HOST:-"localhost"}
|
||||||
|
REDIS_PORT=${REDIS_PORT:-"6379"}
|
||||||
|
|
||||||
|
echo "Testing Redis connection..."
|
||||||
|
|
||||||
|
# Test connection
|
||||||
|
if redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||||
|
echo "✓ Redis is running and accessible"
|
||||||
|
else
|
||||||
|
echo "✗ Cannot connect to Redis at $REDIS_HOST:$REDIS_PORT"
|
||||||
|
echo "Please start Redis: sudo systemctl start redis-server"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure Redis for TaaS
|
||||||
|
echo "Configuring Redis..."
|
||||||
|
|
||||||
|
redis-cli -h $REDIS_HOST -p $REDIS_PORT <<EOF
|
||||||
|
# Set memory limit
|
||||||
|
CONFIG SET maxmemory 2gb
|
||||||
|
CONFIG SET maxmemory-policy allkeys-lru
|
||||||
|
|
||||||
|
# Disable RDB persistence for speed (optional)
|
||||||
|
# CONFIG SET save ""
|
||||||
|
|
||||||
|
# Enable AOF for durability (optional)
|
||||||
|
# CONFIG SET appendonly yes
|
||||||
|
|
||||||
|
# Save configuration
|
||||||
|
CONFIG REWRITE
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Redis configuration complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Connection string: redis://$REDIS_HOST:$REDIS_PORT/"
|
||||||
|
echo ""
|
||||||
|
echo "Update your .env files with:"
|
||||||
|
echo "REDIS_URI=redis://$REDIS_HOST:$REDIS_PORT/"
|
||||||
|
|
||||||
|
# Create some test data
|
||||||
|
echo ""
|
||||||
|
echo "Creating test credit balances..."
|
||||||
|
redis-cli -h $REDIS_HOST -p $REDIS_PORT <<EOF
|
||||||
|
SET credit:10.0.0.1 1073741824
|
||||||
|
SET credit:10.0.0.2 5368709120
|
||||||
|
SET credit:10.0.0.3 10737418240
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✓ Test credit balances created"
|
||||||
|
echo " - 10.0.0.1: 1 GB"
|
||||||
|
echo " - 10.0.0.2: 5 GB"
|
||||||
|
echo " - 10.0.0.3: 10 GB"
|
||||||
Reference in New Issue
Block a user