CalejoControl/scripts/setup-test-environment.sh

795 lines
23 KiB
Bash
Executable File

#!/bin/bash
# Calejo Control Adapter - Test Environment Setup Script
# Sets up mock SCADA and optimizer services for testing
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to display usage
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " --scada-only Only setup mock SCADA services"
echo " --optimizer-only Only setup mock optimizer services"
echo " --with-dashboard Include test dashboard setup"
echo " --clean Clean up existing test services"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 # Setup complete test environment"
echo " $0 --scada-only # Setup only mock SCADA services"
echo " $0 --clean # Clean up test environment"
}
# Parse command line arguments
SCADA_ONLY=false
OPTIMIZER_ONLY=false
WITH_DASHBOARD=false
CLEAN=false
while [[ $# -gt 0 ]]; do
case $1 in
--scada-only)
SCADA_ONLY=true
shift
;;
--optimizer-only)
OPTIMIZER_ONLY=true
shift
;;
--with-dashboard)
WITH_DASHBOARD=true
shift
;;
--clean)
CLEAN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
print_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Check if Docker is available
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed or not in PATH"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
print_error "Docker Compose is not installed or not in PATH"
exit 1
fi
# Function to cleanup test services
cleanup_test_services() {
print_status "Cleaning up test services..."
# Stop and remove test containers
docker-compose -f docker-compose.test.yml down --remove-orphans 2>/dev/null || true
# Remove test network if exists
docker network rm calejo-test-network 2>/dev/null || true
# Remove test volumes
docker volume rm calejo-scada-data 2>/dev/null || true
docker volume rm calejo-optimizer-data 2>/dev/null || true
print_success "Test services cleaned up"
}
# If clean mode, cleanup and exit
if [[ "$CLEAN" == "true" ]]; then
cleanup_test_services
exit 0
fi
# Create test docker-compose file
print_status "Creating test environment configuration..."
cat > docker-compose.test.yml << 'EOF'
version: '3.8'
services:
# Main Calejo Control Adapter
calejo-control-adapter:
build:
context: .
dockerfile: Dockerfile
container_name: calejo-control-adapter-test
ports:
- "8080:8080" # REST API
- "4840:4840" # OPC UA
- "502:502" # Modbus TCP
- "9090:9090" # Prometheus metrics
environment:
- DATABASE_URL=postgresql://calejo:password@postgres:5432/calejo
- JWT_SECRET_KEY=test-secret-key
- API_KEY=test-api-key
- MOCK_SCADA_ENABLED=true
- MOCK_OPTIMIZER_ENABLED=true
- SCADA_MOCK_URL=http://mock-scada:8081
- OPTIMIZER_MOCK_URL=http://mock-optimizer:8082
depends_on:
- postgres
- mock-scada
- mock-optimizer
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
volumes:
- ./logs:/app/logs
- ./config:/app/config
networks:
- calejo-test-network
# PostgreSQL Database
postgres:
image: postgres:15
container_name: calejo-postgres-test
environment:
- POSTGRES_DB=calejo
- POSTGRES_USER=calejo
- POSTGRES_PASSWORD=password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
networks:
- calejo-test-network
# Mock SCADA System
mock-scada:
image: python:3.11-slim
container_name: calejo-mock-scada
ports:
- "8081:8081"
working_dir: /app
volumes:
- ./tests/mock_services:/app
command: >
sh -c "pip install flask requests &&
python mock_scada_server.py"
environment:
- FLASK_ENV=development
- PORT=8081
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- calejo-test-network
# Mock Optimizer Service
mock-optimizer:
image: python:3.11-slim
container_name: calejo-mock-optimizer
ports:
- "8082:8082"
working_dir: /app
volumes:
- ./tests/mock_services:/app
command: >
sh -c "pip install flask requests numpy &&
python mock_optimizer_server.py"
environment:
- FLASK_ENV=development
- PORT=8082
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8082/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- calejo-test-network
# Test Data Generator
test-data-generator:
image: python:3.11-slim
container_name: calejo-test-data-generator
working_dir: /app
volumes:
- ./tests/mock_services:/app
command: >
sh -c "pip install requests &&
python test_data_generator.py"
depends_on:
- calejo-control-adapter
- mock-scada
restart: "no"
networks:
- calejo-test-network
volumes:
postgres_data:
networks:
calejo-test-network:
driver: bridge
EOF
print_success "Test configuration created"
# Create mock services directory
print_status "Creating mock services..."
mkdir -p tests/mock_services
# Create mock SCADA server
cat > tests/mock_services/mock_scada_server.py << 'EOF'
#!/usr/bin/env python3
"""
Mock SCADA Server for Testing
Simulates a real SCADA system with industrial process data
"""
import json
import random
import time
from datetime import datetime
from flask import Flask, jsonify, request
app = Flask(__name__)
# Mock SCADA data storage
scada_data = {
"temperature": {"value": 75.0, "unit": "°C", "min": 50.0, "max": 100.0},
"pressure": {"value": 15.2, "unit": "bar", "min": 10.0, "max": 20.0},
"flow_rate": {"value": 120.5, "unit": "m³/h", "min": 80.0, "max": 150.0},
"level": {"value": 65.3, "unit": "%", "min": 0.0, "max": 100.0},
"power": {"value": 450.7, "unit": "kW", "min": 300.0, "max": 600.0},
"status": {"value": "RUNNING", "options": ["STOPPED", "RUNNING", "FAULT"]},
"efficiency": {"value": 87.5, "unit": "%", "min": 0.0, "max": 100.0}
}
# Equipment status
equipment_status = {
"pump_1": "RUNNING",
"pump_2": "STOPPED",
"valve_1": "OPEN",
"valve_2": "CLOSED",
"compressor": "RUNNING",
"heater": "ON"
}
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({"status": "healthy", "service": "mock-scada"})
@app.route('/api/v1/data', methods=['GET'])
def get_all_data():
"""Get all SCADA data"""
# Simulate data variation
for key in scada_data:
if key != "status":
current = scada_data[key]
variation = random.uniform(-2.0, 2.0)
new_value = current["value"] + variation
# Keep within bounds
new_value = max(current["min"], min(new_value, current["max"]))
scada_data[key]["value"] = round(new_value, 2)
return jsonify({
"timestamp": datetime.utcnow().isoformat(),
"data": scada_data,
"equipment": equipment_status
})
@app.route('/api/v1/data/<tag>', methods=['GET'])
def get_specific_data(tag):
"""Get specific SCADA data tag"""
if tag in scada_data:
# Simulate variation for numeric values
if tag != "status":
current = scada_data[tag]
variation = random.uniform(-1.0, 1.0)
new_value = current["value"] + variation
new_value = max(current["min"], min(new_value, current["max"]))
scada_data[tag]["value"] = round(new_value, 2)
return jsonify({
"tag": tag,
"value": scada_data[tag]["value"],
"unit": scada_data[tag].get("unit", ""),
"timestamp": datetime.utcnow().isoformat()
})
else:
return jsonify({"error": "Tag not found"}), 404
@app.route('/api/v1/control/<equipment>', methods=['POST'])
def control_equipment(equipment):
"""Control SCADA equipment"""
data = request.get_json()
if not data or 'command' not in data:
return jsonify({"error": "Missing command"}), 400
command = data['command']
if equipment in equipment_status:
# Simulate control logic
if command in ["START", "STOP", "OPEN", "CLOSE", "ON", "OFF"]:
old_status = equipment_status[equipment]
equipment_status[equipment] = command
return jsonify({
"equipment": equipment,
"previous_status": old_status,
"current_status": command,
"timestamp": datetime.utcnow().isoformat(),
"message": f"Equipment {equipment} changed from {old_status} to {command}"
})
else:
return jsonify({"error": "Invalid command"}), 400
else:
return jsonify({"error": "Equipment not found"}), 404
@app.route('/api/v1/alarms', methods=['GET'])
def get_alarms():
"""Get current alarms"""
# Simulate occasional alarms
alarms = []
# Temperature alarm
if scada_data["temperature"]["value"] > 90:
alarms.append({
"id": "TEMP_HIGH",
"message": "High temperature alarm",
"severity": "HIGH",
"timestamp": datetime.utcnow().isoformat()
})
# Pressure alarm
if scada_data["pressure"]["value"] > 18:
alarms.append({
"id": "PRESS_HIGH",
"message": "High pressure alarm",
"severity": "MEDIUM",
"timestamp": datetime.utcnow().isoformat()
})
return jsonify({"alarms": alarms})
if __name__ == '__main__':
import os
port = int(os.getenv('PORT', 8081))
app.run(host='0.0.0.0', port=port, debug=True)
EOF
print_success "Mock SCADA server created"
# Create mock optimizer server
cat > tests/mock_services/mock_optimizer_server.py << 'EOF'
#!/usr/bin/env python3
"""
Mock Optimizer Server for Testing
Simulates an optimization service for industrial processes
"""
import json
import random
import numpy as np
from datetime import datetime, timedelta
from flask import Flask, jsonify, request
app = Flask(__name__)
# Mock optimization models
optimization_models = {
"energy_optimization": {
"name": "Energy Consumption Optimizer",
"description": "Optimizes energy usage across processes",
"parameters": ["power_load", "time_of_day", "production_rate"]
},
"production_optimization": {
"name": "Production Efficiency Optimizer",
"description": "Maximizes production efficiency",
"parameters": ["raw_material_quality", "machine_utilization", "operator_skill"]
},
"cost_optimization": {
"name": "Cost Reduction Optimizer",
"description": "Minimizes operational costs",
"parameters": ["energy_cost", "labor_cost", "maintenance_cost"]
}
}
# Optimization history
optimization_history = []
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({"status": "healthy", "service": "mock-optimizer"})
@app.route('/api/v1/models', methods=['GET'])
def get_models():
"""Get available optimization models"""
return jsonify({"models": optimization_models})
@app.route('/api/v1/optimize/<model_name>', methods=['POST'])
def optimize(model_name):
"""Run optimization for a specific model"""
data = request.get_json()
if not data:
return jsonify({"error": "No input data provided"}), 400
if model_name not in optimization_models:
return jsonify({"error": "Model not found"}), 404
# Simulate optimization processing
processing_time = random.uniform(0.5, 3.0)
# Generate optimization results
if model_name == "energy_optimization":
result = {
"optimal_power_setpoint": random.uniform(400, 500),
"recommended_actions": [
"Reduce compressor load during peak hours",
"Optimize pump sequencing",
"Adjust temperature setpoints"
],
"estimated_savings": random.uniform(5, 15),
"confidence": random.uniform(0.7, 0.95)
}
elif model_name == "production_optimization":
result = {
"optimal_production_rate": random.uniform(80, 120),
"recommended_actions": [
"Adjust raw material mix",
"Optimize machine speeds",
"Improve operator scheduling"
],
"efficiency_gain": random.uniform(3, 12),
"confidence": random.uniform(0.75, 0.92)
}
elif model_name == "cost_optimization":
result = {
"optimal_cost_structure": {
"energy": random.uniform(40, 60),
"labor": random.uniform(25, 35),
"maintenance": random.uniform(10, 20)
},
"recommended_actions": [
"Shift energy consumption to off-peak",
"Optimize maintenance schedules",
"Improve labor allocation"
],
"cost_reduction": random.uniform(8, 20),
"confidence": random.uniform(0.8, 0.98)
}
# Record optimization
optimization_record = {
"model": model_name,
"timestamp": datetime.utcnow().isoformat(),
"input_data": data,
"result": result,
"processing_time": processing_time
}
optimization_history.append(optimization_record)
return jsonify({
"optimization_id": len(optimization_history),
"model": model_name,
"result": result,
"processing_time": processing_time,
"timestamp": datetime.utcnow().isoformat()
})
@app.route('/api/v1/history', methods=['GET'])
def get_history():
"""Get optimization history"""
limit = request.args.get('limit', 10, type=int)
return jsonify({
"history": optimization_history[-limit:],
"total_optimizations": len(optimization_history)
})
@app.route('/api/v1/forecast', methods=['POST'])
def forecast():
"""Generate forecasts based on current data"""
data = request.get_json()
if not data or 'hours' not in data:
return jsonify({"error": "Missing forecast hours"}), 400
hours = data['hours']
# Generate mock forecast
forecast_data = []
current_time = datetime.utcnow()
for i in range(hours):
forecast_time = current_time + timedelta(hours=i)
forecast_data.append({
"timestamp": forecast_time.isoformat(),
"energy_consumption": random.uniform(400, 600),
"production_rate": random.uniform(85, 115),
"efficiency": random.uniform(80, 95),
"cost": random.uniform(45, 65)
})
return jsonify({
"forecast": forecast_data,
"generated_at": current_time.isoformat(),
"horizon_hours": hours
})
if __name__ == '__main__':
import os
port = int(os.getenv('PORT', 8082))
app.run(host='0.0.0.0', port=port, debug=True)
EOF
print_success "Mock optimizer server created"
# Create test data generator
cat > tests/mock_services/test_data_generator.py << 'EOF'
#!/usr/bin/env python3
"""
Test Data Generator
Generates realistic test data for the Calejo Control Adapter
"""
import requests
import time
import random
import json
from datetime import datetime
# Configuration
CALEJO_API_URL = "http://calejo-control-adapter-test:8080"
SCADA_MOCK_URL = "http://mock-scada:8081"
OPTIMIZER_MOCK_URL = "http://mock-optimizer:8082"
# Test scenarios
test_scenarios = [
"normal_operation",
"high_load",
"low_efficiency",
"alarm_condition",
"optimization_test"
]
def test_health_checks():
"""Test health of all services"""
print("🔍 Testing service health...")
services = [
("Calejo Control Adapter", f"{CALEJO_API_URL}/health"),
("Mock SCADA", f"{SCADA_MOCK_URL}/health"),
("Mock Optimizer", f"{OPTIMIZER_MOCK_URL}/health")
]
for service_name, url in services:
try:
response = requests.get(url, timeout=5)
if response.status_code == 200:
print(f" ✅ {service_name}: Healthy")
else:
print(f" ❌ {service_name}: Unhealthy (Status: {response.status_code})")
except Exception as e:
print(f" ❌ {service_name}: Connection failed - {e}")
def generate_scada_data():
"""Generate and send SCADA data"""
print("📊 Generating SCADA test data...")
try:
# Get current SCADA data
response = requests.get(f"{SCADA_MOCK_URL}/api/v1/data")
if response.status_code == 200:
data = response.json()
print(f" 📈 Current SCADA data: {len(data.get('data', {}))} tags")
# Send some control commands
equipment_to_control = ["pump_1", "valve_1", "compressor"]
for equipment in equipment_to_control:
command = random.choice(["START", "STOP", "OPEN", "CLOSE"])
try:
control_response = requests.post(
f"{SCADA_MOCK_URL}/api/v1/control/{equipment}",
json={"command": command},
timeout=5
)
if control_response.status_code == 200:
print(f" 🎛️ Controlled {equipment}: {command}")
except:
pass
except Exception as e:
print(f" ❌ SCADA data generation failed: {e}")
def test_optimization():
"""Test optimization scenarios"""
print("🧠 Testing optimization...")
try:
# Get available models
response = requests.get(f"{OPTIMIZER_MOCK_URL}/api/v1/models")
if response.status_code == 200:
models = response.json().get('models', {})
# Test each model
for model_name in models:
test_data = {
"power_load": random.uniform(400, 600),
"time_of_day": random.randint(0, 23),
"production_rate": random.uniform(80, 120)
}
opt_response = requests.post(
f"{OPTIMIZER_MOCK_URL}/api/v1/optimize/{model_name}",
json=test_data,
timeout=10
)
if opt_response.status_code == 200:
result = opt_response.json()
print(f" ✅ {model_name}: Optimization completed")
print(f" Processing time: {result.get('processing_time', 0):.2f}s")
else:
print(f" ❌ {model_name}: Optimization failed")
except Exception as e:
print(f" ❌ Optimization test failed: {e}")
def test_calejo_api():
"""Test Calejo Control Adapter API"""
print("🌐 Testing Calejo API...")
endpoints = [
"/health",
"/dashboard",
"/api/v1/status",
"/api/v1/metrics"
]
for endpoint in endpoints:
try:
response = requests.get(f"{CALEJO_API_URL}{endpoint}", timeout=5)
if response.status_code == 200:
print(f" ✅ {endpoint}: Accessible")
else:
print(f" ⚠️ {endpoint}: Status {response.status_code}")
except Exception as e:
print(f" ❌ {endpoint}: Failed - {e}")
def run_comprehensive_test():
"""Run comprehensive test scenario"""
print("\n🚀 Starting comprehensive test scenario...")
print("=" * 50)
# Test all components
test_health_checks()
print()
generate_scada_data()
print()
test_optimization()
print()
test_calejo_api()
print()
print("✅ Comprehensive test completed!")
print("\n📋 Test Summary:")
print(" • Service health checks")
print(" • SCADA data generation and control")
print(" • Optimization model testing")
print(" • Calejo API endpoint validation")
if __name__ == "__main__":
# Wait a bit for services to start
print("⏳ Waiting for services to initialize...")
time.sleep(10)
run_comprehensive_test()
EOF
print_success "Test data generator created"
# Start test services
print_status "Starting test services..."
docker-compose -f docker-compose.test.yml up -d
# Wait for services to start
print_status "Waiting for services to initialize..."
sleep 30
# Run health checks
print_status "Running health checks..."
# Check Calejo Control Adapter
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
print_success "Calejo Control Adapter is healthy"
else
print_error "Calejo Control Adapter health check failed"
fi
# Check Mock SCADA
if curl -f http://localhost:8081/health > /dev/null 2>&1; then
print_success "Mock SCADA is healthy"
else
print_error "Mock SCADA health check failed"
fi
# Check Mock Optimizer
if curl -f http://localhost:8082/health > /dev/null 2>&1; then
print_success "Mock Optimizer is healthy"
else
print_error "Mock Optimizer health check failed"
fi
# Run test data generator
print_status "Running test data generator..."
docker-compose -f docker-compose.test.yml run --rm test-data-generator
print_success "Test environment setup completed!"
# Display access information
print ""
echo "=================================================="
echo " TEST ENVIRONMENT READY"
echo "=================================================="
echo ""
echo "🌐 Access URLs:"
echo " Calejo Dashboard: http://localhost:8080/dashboard"
echo " Mock SCADA API: http://localhost:8081/api/v1/data"
echo " Mock Optimizer API: http://localhost:8082/api/v1/models"
echo " PostgreSQL: localhost:5432"
echo ""
echo "🔧 Management Commands:"
echo " View logs: docker-compose -f docker-compose.test.yml logs -f"
echo " Stop services: docker-compose -f docker-compose.test.yml down"
echo " Cleanup: ./scripts/setup-test-environment.sh --clean"
echo ""
echo "🧪 Test Commands:"
echo " Run tests: python -m pytest tests/"
echo " Generate data: docker-compose -f docker-compose.test.yml run --rm test-data-generator"
echo ""
echo "=================================================="