#!/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: - "8081:8081" # 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:8081/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/', 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/', 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/', 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:8081/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:8081/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 "=================================================="