diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..6852b61 --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -0,0 +1,296 @@ +# Calejo Control Adapter - Deployment Guide + +This guide provides comprehensive instructions for deploying the Calejo Control Adapter in on-premises customer environments. + +## ๐Ÿš€ Quick Deployment + +### Automated Deployment (Recommended) + +For quick and easy deployment, use the automated deployment script: + +```bash +# Run as root for system-wide installation +sudo ./deploy-onprem.sh +``` + +This script will: +- Check prerequisites (Docker, Docker Compose) +- Create necessary directories +- Copy all required files +- Create systemd service for automatic startup +- Build and start all services +- Create backup and health check scripts + +### Manual Deployment + +If you prefer manual deployment: + +1. **Install Prerequisites** + ```bash + # Install Docker + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + + # Install Docker Compose + sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + ``` + +2. **Deploy Application** + ```bash + # Create directories + sudo mkdir -p /opt/calejo-control-adapter + sudo mkdir -p /var/log/calejo + sudo mkdir -p /etc/calejo + sudo mkdir -p /var/backup/calejo + + # Copy files + sudo cp -r ./* /opt/calejo-control-adapter/ + sudo cp config/settings.py /etc/calejo/ + + # Set permissions + sudo chmod +x /opt/calejo-control-adapter/scripts/*.sh + + # Build and start + cd /opt/calejo-control-adapter + sudo docker-compose build + sudo docker-compose up -d + ``` + +## ๐Ÿงช Testing the Deployment + +### End-to-End Testing + +Test the complete system with mock SCADA and optimization servers: + +```bash +# Run comprehensive end-to-end tests +python test-e2e-deployment.py +``` + +This will: +- Start mock SCADA server +- Start mock optimization server +- Start main application +- Test all endpoints and functionality +- Validate integration between components + +### Individual Component Testing + +```bash +# Test mock SCADA server +python mock-scada-server.py + +# Test mock optimization server +python mock-optimization-server.py + +# Test local dashboard functionality +python test_dashboard_local.py + +# Test deployment health +./validate-deployment.sh +``` + +## ๐Ÿ” Deployment Validation + +After deployment, validate that everything is working correctly: + +```bash +# Run comprehensive validation +./validate-deployment.sh +``` + +This checks: +- โœ… System resources (disk, memory, CPU) +- โœ… Docker container status +- โœ… Application endpoints +- โœ… Configuration validity +- โœ… Log files +- โœ… Security configuration +- โœ… Backup setup + +## ๐Ÿ“Š Mock Systems for Testing + +### Mock SCADA Server + +The mock SCADA server (`mock-scada-server.py`) simulates: +- **OPC UA Server** on port 4840 +- **Modbus TCP Server** on port 502 +- **Real-time process data** (temperature, pressure, flow, level) +- **Historical data trends** +- **Alarm simulation** + +### Mock Optimization Server + +The mock optimization server (`mock-optimization-server.py`) simulates: +- **Multiple optimization strategies** +- **Market data simulation** +- **Setpoint calculations** +- **Cost and energy savings analysis** +- **Confidence scoring** + +## ๐Ÿ”ง Management Commands + +### Service Management + +```bash +# Start service +sudo systemctl start calejo-control-adapter + +# Stop service +sudo systemctl stop calejo-control-adapter + +# Check status +sudo systemctl status calejo-control-adapter + +# Enable auto-start +sudo systemctl enable calejo-control-adapter +``` + +### Application Management + +```bash +# Health check +/opt/calejo-control-adapter/scripts/health-check.sh + +# Full backup +/opt/calejo-control-adapter/scripts/backup-full.sh + +# Restore from backup +/opt/calejo-control-adapter/scripts/restore-full.sh + +# View logs +sudo docker-compose logs -f app +``` + +## ๐Ÿ“ Directory Structure + +``` +/opt/calejo-control-adapter/ # Main application directory +โ”œโ”€โ”€ src/ # Source code +โ”œโ”€โ”€ static/ # Static files (dashboard) +โ”œโ”€โ”€ config/ # Configuration files +โ”œโ”€โ”€ scripts/ # Management scripts +โ”œโ”€โ”€ monitoring/ # Monitoring configuration +โ”œโ”€โ”€ tests/ # Test files +โ””โ”€โ”€ docker-compose.yml # Docker Compose configuration + +/var/log/calejo/ # Application logs +/etc/calejo/ # Configuration files +/var/backup/calejo/ # Backup files +``` + +## ๐ŸŒ Access Points + +After deployment, access the system at: + +- **Dashboard**: `http://:8080/dashboard` +- **REST API**: `http://:8080` +- **Health Check**: `http://:8080/health` +- **Mock SCADA (OPC UA)**: `opc.tcp://:4840` +- **Mock SCADA (Modbus)**: `:502` + +## ๐Ÿ”’ Security Considerations + +### Default Credentials + +The deployment includes security validation that warns about: +- Default database credentials +- Unsecured communication +- Open ports + +### Recommended Security Practices + +1. **Change default passwords** in configuration +2. **Enable authentication** in production +3. **Use SSL/TLS** for external communication +4. **Configure firewall** to restrict access +5. **Regular security updates** + +## ๐Ÿ“ˆ Monitoring and Maintenance + +### Health Monitoring + +```bash +# Regular health checks +/opt/calejo-control-adapter/scripts/health-check.sh + +# Monitor logs +sudo tail -f /var/log/calejo/*.log +``` + +### Backup Strategy + +```bash +# Schedule regular backups (add to crontab) +0 2 * * * /opt/calejo-control-adapter/scripts/backup-full.sh + +# Manual backup +/opt/calejo-control-adapter/scripts/backup-full.sh +``` + +### Performance Monitoring + +The deployment includes: +- **Prometheus** metrics collection +- **Grafana** dashboards +- **Health monitoring** endpoints +- **Log aggregation** + +## ๐Ÿ› Troubleshooting + +### Common Issues + +1. **Application not starting** + ```bash + # Check Docker status + sudo systemctl status docker + + # Check application logs + sudo docker-compose logs app + ``` + +2. **Dashboard not accessible** + ```bash + # Check if application is running + curl http://localhost:8080/health + + # Check firewall settings + sudo ufw status + ``` + +3. **Mock servers not working** + ```bash + # Check if required ports are available + sudo netstat -tulpn | grep -E ':(4840|502|8081)' + ``` + +### Log Files + +- Application logs: `/var/log/calejo/` +- Docker logs: `sudo docker-compose logs` +- System logs: `/var/log/syslog` + +## ๐Ÿ“ž Support + +For deployment issues: + +1. Check this deployment guide +2. Run validation script: `./validate-deployment.sh` +3. Check logs in `/var/log/calejo/` +4. Review test results from `test-e2e-deployment.py` + +## ๐ŸŽฏ Next Steps After Deployment + +1. **Validate deployment** with `./validate-deployment.sh` +2. **Run end-to-end tests** with `python test-e2e-deployment.py` +3. **Configure monitoring** in Grafana +4. **Set up backups** with cron jobs +5. **Test integration** with real SCADA/optimization systems +6. **Train users** on dashboard usage + +--- + +**Deployment Status**: โœ… Ready for Production +**Last Updated**: $(date) +**Version**: 1.0.0 \ No newline at end of file diff --git a/deploy-onprem.sh b/deploy-onprem.sh new file mode 100755 index 0000000..8604017 --- /dev/null +++ b/deploy-onprem.sh @@ -0,0 +1,388 @@ +#!/bin/bash + +# Calejo Control Adapter - On-Prem Deployment Script +# This script automates the deployment process for customer on-prem installations + +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 + +# Configuration +DEPLOYMENT_DIR="/opt/calejo-control-adapter" +LOG_DIR="/var/log/calejo" +CONFIG_DIR="/etc/calejo" +BACKUP_DIR="/var/backup/calejo" + +# 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 check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + print_error "This script must be run as root for system-wide installation" + exit 1 + fi +} + +# Function to check prerequisites +check_prerequisites() { + print_status "Checking prerequisites..." + + # Check Docker + if ! command -v docker &> /dev/null; then + print_error "Docker is not installed. Please install Docker first." + exit 1 + fi + + # Check Docker Compose + if ! command -v docker-compose &> /dev/null; then + print_error "Docker Compose is not installed. Please install Docker Compose first." + exit 1 + fi + + # Check available disk space + local available_space=$(df / | awk 'NR==2 {print $4}') + if [[ $available_space -lt 1048576 ]]; then # Less than 1GB + print_warning "Low disk space available: ${available_space}KB" + fi + + print_success "Prerequisites check passed" +} + +# Function to create directories +create_directories() { + print_status "Creating directories..." + + mkdir -p $DEPLOYMENT_DIR + mkdir -p $LOG_DIR + mkdir -p $CONFIG_DIR + mkdir -p $BACKUP_DIR + mkdir -p $DEPLOYMENT_DIR/monitoring + mkdir -p $DEPLOYMENT_DIR/scripts + mkdir -p $DEPLOYMENT_DIR/database + + print_success "Directories created" +} + +# Function to copy files +copy_files() { + print_status "Copying deployment files..." + + # Copy main application files + cp -r ./* $DEPLOYMENT_DIR/ + + # Copy configuration files + cp config/settings.py $CONFIG_DIR/ + cp docker-compose.yml $DEPLOYMENT_DIR/ + cp docker-compose.test.yml $DEPLOYMENT_DIR/ + + # Copy scripts + cp scripts/* $DEPLOYMENT_DIR/scripts/ + cp test-deployment.sh $DEPLOYMENT_DIR/ + cp test_dashboard_local.py $DEPLOYMENT_DIR/ + + # Copy monitoring configuration + cp -r monitoring/* $DEPLOYMENT_DIR/monitoring/ + + # Set permissions + chmod +x $DEPLOYMENT_DIR/scripts/*.sh + chmod +x $DEPLOYMENT_DIR/test-deployment.sh + + print_success "Files copied to deployment directory" +} + +# Function to create systemd service +create_systemd_service() { + print_status "Creating systemd service..." + + cat > /etc/systemd/system/calejo-control-adapter.service << EOF +[Unit] +Description=Calejo Control Adapter +Requires=docker.service +After=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=$DEPLOYMENT_DIR +ExecStart=/usr/bin/docker-compose up -d +ExecStop=/usr/bin/docker-compose down +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target +EOF + + systemctl daemon-reload + print_success "Systemd service created" +} + +# Function to create backup script +create_backup_script() { + print_status "Creating backup script..." + + cat > $DEPLOYMENT_DIR/scripts/backup-full.sh << 'EOF' +#!/bin/bash +# Full backup script for Calejo Control Adapter + +BACKUP_DIR="/var/backup/calejo" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="calejo-backup-$TIMESTAMP.tar.gz" + +mkdir -p $BACKUP_DIR + +# Stop services +echo "Stopping services..." +docker-compose down + +# Create backup +echo "Creating backup..." +tar -czf $BACKUP_DIR/$BACKUP_FILE \ + --exclude=node_modules \ + --exclude=__pycache__ \ + --exclude=*.pyc \ + . + +# Start services +echo "Starting services..." +docker-compose up -d + +echo "Backup created: $BACKUP_DIR/$BACKUP_FILE" +echo "Backup size: $(du -h $BACKUP_DIR/$BACKUP_FILE | cut -f1)" +EOF + + chmod +x $DEPLOYMENT_DIR/scripts/backup-full.sh + print_success "Backup script created" +} + +# Function to create restore script +create_restore_script() { + print_status "Creating restore script..." + + cat > $DEPLOYMENT_DIR/scripts/restore-full.sh << 'EOF' +#!/bin/bash +# Full restore script for Calejo Control Adapter + +BACKUP_DIR="/var/backup/calejo" + +if [ $# -eq 0 ]; then + echo "Usage: $0 " + echo "Available backups:" + ls -la $BACKUP_DIR/calejo-backup-*.tar.gz 2>/dev/null || echo "No backups found" + exit 1 +fi + +BACKUP_FILE="$1" + +if [ ! -f "$BACKUP_FILE" ]; then + echo "Backup file not found: $BACKUP_FILE" + exit 1 +fi + +# Stop services +echo "Stopping services..." +docker-compose down + +# Restore backup +echo "Restoring from backup..." +tar -xzf "$BACKUP_FILE" -C . + +# Start services +echo "Starting services..." +docker-compose up -d + +echo "Restore completed from: $BACKUP_FILE" +EOF + + chmod +x $DEPLOYMENT_DIR/scripts/restore-full.sh + print_success "Restore script created" +} + +# Function to create health check script +create_health_check_script() { + print_status "Creating health check script..." + + cat > $DEPLOYMENT_DIR/scripts/health-check.sh << 'EOF' +#!/bin/bash +# Health check script for Calejo Control Adapter + +set -e + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +check_service() { + local service_name=$1 + local port=$2 + local endpoint=$3 + + if curl -s "http://localhost:$port$endpoint" > /dev/null; then + echo -e "${GREEN}โœ“${NC} $service_name is running on port $port" + return 0 + else + echo -e "${RED}โœ—${NC} $service_name is not responding on port $port" + return 1 + fi +} + +echo "Running health checks..." + +# Check main application +check_service "Main Application" 8080 "/health" + +# Check dashboard +check_service "Dashboard" 8080 "/dashboard" + +# Check API endpoints +check_service "REST API" 8080 "/api/v1/status" + +# Check if containers are running +if docker-compose ps | grep -q "Up"; then + echo -e "${GREEN}โœ“${NC} All Docker containers are running" +else + echo -e "${RED}โœ—${NC} Some Docker containers are not running" + docker-compose ps +fi + +# Check disk space +echo "" +echo "System resources:" +df -h / | awk 'NR==2 {print "Disk usage: " $5 " (" $3 "/" $2 ")"}' + +# Check memory +free -h | awk 'NR==2 {print "Memory usage: " $3 "/" $2}' + +echo "" +echo "Health check completed" +EOF + + chmod +x $DEPLOYMENT_DIR/scripts/health-check.sh + print_success "Health check script created" +} + +# Function to build and start services +build_and_start_services() { + print_status "Building and starting services..." + + cd $DEPLOYMENT_DIR + + # Build the application + docker-compose build + + # Start services + docker-compose up -d + + # Wait for services to be ready + print_status "Waiting for services to start..." + for i in {1..30}; do + if curl -s http://localhost:8080/health > /dev/null 2>&1; then + print_success "Services started successfully" + break + fi + echo " Waiting... (attempt $i/30)" + sleep 2 + + if [ $i -eq 30 ]; then + print_error "Services failed to start within 60 seconds" + docker-compose logs + exit 1 + fi + done +} + +# Function to display deployment information +display_deployment_info() { + print_success "Deployment completed successfully!" + echo "" + echo "==================================================" + echo " DEPLOYMENT INFORMATION" + echo "==================================================" + echo "" + echo "๐Ÿ“Š Access URLs:" + echo " Dashboard: http://$(hostname -I | awk '{print $1}'):8080/dashboard" + echo " REST API: http://$(hostname -I | awk '{print $1}'):8080" + echo " Health Check: http://$(hostname -I | awk '{print $1}'):8080/health" + echo "" + echo "๐Ÿ”ง Management Commands:" + echo " Start: systemctl start calejo-control-adapter" + echo " Stop: systemctl stop calejo-control-adapter" + echo " Status: systemctl status calejo-control-adapter" + echo " Health Check: $DEPLOYMENT_DIR/scripts/health-check.sh" + echo " Backup: $DEPLOYMENT_DIR/scripts/backup-full.sh" + echo "" + echo "๐Ÿ“ Important Directories:" + echo " Application: $DEPLOYMENT_DIR" + echo " Logs: $LOG_DIR" + echo " Configuration: $CONFIG_DIR" + echo " Backups: $BACKUP_DIR" + echo "" + echo "๐Ÿ“š Documentation:" + echo " Quick Start: $DEPLOYMENT_DIR/QUICKSTART.md" + echo " Dashboard: $DEPLOYMENT_DIR/DASHBOARD.md" + echo " Deployment: $DEPLOYMENT_DIR/DEPLOYMENT.md" + echo "" + echo "==================================================" +} + +# Main deployment function +main() { + echo "" + echo "๐Ÿš€ Calejo Control Adapter - On-Prem Deployment" + echo "==================================================" + echo "" + + # Check if running as root + check_root + + # Check prerequisites + check_prerequisites + + # Create directories + create_directories + + # Copy files + copy_files + + # Create systemd service + create_systemd_service + + # Create management scripts + create_backup_script + create_restore_script + create_health_check_script + + # Build and start services + build_and_start_services + + # Display deployment information + display_deployment_info + + echo "" + print_success "On-prem deployment completed!" + echo "" +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/mock-optimization-server.py b/mock-optimization-server.py new file mode 100755 index 0000000..59904e9 --- /dev/null +++ b/mock-optimization-server.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +""" +Mock Optimization Server for Testing +Simulates an optimization system that provides setpoints and control strategies +""" + +import asyncio +import logging +import random +import time +import json +from datetime import datetime +from typing import Dict, Any, List +from dataclasses import dataclass +from enum import Enum + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class OptimizationStrategy(Enum): + """Optimization strategies""" + ENERGY_EFFICIENCY = "energy_efficiency" + COST_OPTIMIZATION = "cost_optimization" + PRODUCTION_MAXIMIZATION = "production_maximization" + QUALITY_OPTIMIZATION = "quality_optimization" + + +@dataclass +class OptimizationResult: + """Result of optimization calculation""" + strategy: OptimizationStrategy + setpoints: Dict[str, float] + cost_savings: float + energy_savings: float + production_increase: float + quality_improvement: float + confidence: float + timestamp: datetime + + +class MockOptimizationServer: + """Mock optimization server that simulates optimization calculations""" + + def __init__(self, port: int = 8081): + self.port = port + self.running = False + self.current_strategy = OptimizationStrategy.ENERGY_EFFICIENCY + + # Historical optimization results + self.optimization_history: List[OptimizationResult] = [] + + # Current process state (would come from SCADA) + self.current_state = { + 'temperature': 25.0, + 'pressure': 101.3, + 'flow_rate': 100.0, + 'level': 50.0, + 'energy_consumption': 150.0, + 'production_rate': 85.0, + 'quality_metric': 92.0, + 'operating_cost': 1250.0 + } + + # Market and external factors + self.market_data = { + 'energy_price': 0.12, # $/kWh + 'raw_material_cost': 45.0, # $/ton + 'product_price': 150.0, # $/unit + 'demand_factor': 0.95 + } + + def calculate_optimization(self, strategy: OptimizationStrategy) -> OptimizationResult: + """Calculate optimization based on current state and strategy""" + + # Simulate optimization calculation + base_setpoints = { + 'temperature_setpoint': 75.0, + 'pressure_setpoint': 105.0, + 'flow_setpoint': 120.0, + 'level_setpoint': 60.0 + } + + # Adjust setpoints based on strategy + if strategy == OptimizationStrategy.ENERGY_EFFICIENCY: + setpoints = { + 'temperature_setpoint': base_setpoints['temperature_setpoint'] - 2.0, + 'pressure_setpoint': base_setpoints['pressure_setpoint'] - 1.0, + 'flow_setpoint': base_setpoints['flow_setpoint'] - 5.0, + 'level_setpoint': base_setpoints['level_setpoint'] + } + cost_savings = random.uniform(5.0, 15.0) + energy_savings = random.uniform(8.0, 20.0) + production_increase = random.uniform(-2.0, 2.0) + quality_improvement = random.uniform(-1.0, 1.0) + + elif strategy == OptimizationStrategy.COST_OPTIMIZATION: + setpoints = { + 'temperature_setpoint': base_setpoints['temperature_setpoint'] - 1.0, + 'pressure_setpoint': base_setpoints['pressure_setpoint'] - 0.5, + 'flow_setpoint': base_setpoints['flow_setpoint'] - 3.0, + 'level_setpoint': base_setpoints['level_setpoint'] + 5.0 + } + cost_savings = random.uniform(10.0, 25.0) + energy_savings = random.uniform(5.0, 12.0) + production_increase = random.uniform(1.0, 5.0) + quality_improvement = random.uniform(0.0, 2.0) + + elif strategy == OptimizationStrategy.PRODUCTION_MAXIMIZATION: + setpoints = { + 'temperature_setpoint': base_setpoints['temperature_setpoint'] + 3.0, + 'pressure_setpoint': base_setpoints['pressure_setpoint'] + 2.0, + 'flow_setpoint': base_setpoints['flow_setpoint'] + 10.0, + 'level_setpoint': base_setpoints['level_setpoint'] - 5.0 + } + cost_savings = random.uniform(-5.0, 5.0) + energy_savings = random.uniform(-10.0, 0.0) + production_increase = random.uniform(8.0, 15.0) + quality_improvement = random.uniform(-3.0, 0.0) + + else: # QUALITY_OPTIMIZATION + setpoints = { + 'temperature_setpoint': base_setpoints['temperature_setpoint'] + 1.0, + 'pressure_setpoint': base_setpoints['pressure_setpoint'] + 0.5, + 'flow_setpoint': base_setpoints['flow_setpoint'] - 2.0, + 'level_setpoint': base_setpoints['level_setpoint'] + 2.0 + } + cost_savings = random.uniform(2.0, 8.0) + energy_savings = random.uniform(3.0, 8.0) + production_increase = random.uniform(-1.0, 1.0) + quality_improvement = random.uniform(5.0, 12.0) + + # Add some randomness to simulate real optimization + for key in setpoints: + setpoints[key] += random.uniform(-1.0, 1.0) + + # Calculate confidence based on strategy and current conditions + confidence = random.uniform(0.7, 0.95) + + result = OptimizationResult( + strategy=strategy, + setpoints=setpoints, + cost_savings=cost_savings, + energy_savings=energy_savings, + production_increase=production_increase, + quality_improvement=quality_improvement, + confidence=confidence, + timestamp=datetime.now() + ) + + return result + + def get_optimal_strategy(self) -> OptimizationStrategy: + """Determine the best strategy based on current conditions""" + + # Simple heuristic based on current state and market conditions + if self.market_data['energy_price'] > 0.15: + return OptimizationStrategy.ENERGY_EFFICIENCY + elif self.market_data['demand_factor'] > 1.1: + return OptimizationStrategy.PRODUCTION_MAXIMIZATION + elif self.current_state['quality_metric'] < 90.0: + return OptimizationStrategy.QUALITY_OPTIMIZATION + else: + return OptimizationStrategy.COST_OPTIMIZATION + + async def update_market_data(self): + """Simulate changing market conditions""" + while self.running: + try: + # Simulate market fluctuations + self.market_data['energy_price'] += random.uniform(-0.01, 0.01) + self.market_data['energy_price'] = max(0.08, min(0.20, self.market_data['energy_price'])) + + self.market_data['raw_material_cost'] += random.uniform(-1.0, 1.0) + self.market_data['raw_material_cost'] = max(40.0, min(60.0, self.market_data['raw_material_cost'])) + + self.market_data['demand_factor'] += random.uniform(-0.05, 0.05) + self.market_data['demand_factor'] = max(0.8, min(1.3, self.market_data['demand_factor'])) + + await asyncio.sleep(30) # Update every 30 seconds + + except Exception as e: + logger.error(f"Error updating market data: {e}") + await asyncio.sleep(10) + + async def run_optimization_cycle(self): + """Run optimization cycles periodically""" + while self.running: + try: + # Get optimal strategy + strategy = self.get_optimal_strategy() + self.current_strategy = strategy + + # Calculate optimization + result = self.calculate_optimization(strategy) + + # Store in history + self.optimization_history.append(result) + + # Keep only last 100 optimizations + if len(self.optimization_history) > 100: + self.optimization_history = self.optimization_history[-100:] + + logger.info(f"Optimization completed: {strategy.value} - Confidence: {result.confidence:.2f}") + + await asyncio.sleep(60) # Run optimization every minute + + except Exception as e: + logger.error(f"Error in optimization cycle: {e}") + await asyncio.sleep(10) + + def get_status(self) -> Dict[str, Any]: + """Get server status""" + latest_result = self.optimization_history[-1] if self.optimization_history else None + + return { + 'running': self.running, + 'current_strategy': self.current_strategy.value if self.current_strategy else None, + 'market_data': self.market_data, + 'optimization_count': len(self.optimization_history), + 'latest_optimization': { + 'strategy': latest_result.strategy.value if latest_result else None, + 'setpoints': latest_result.setpoints if latest_result else {}, + 'confidence': latest_result.confidence if latest_result else 0.0, + 'timestamp': latest_result.timestamp.isoformat() if latest_result else None + } if latest_result else None + } + + async def start(self): + """Start the mock optimization server""" + if self.running: + return + + self.running = True + + # Start background tasks + self.market_task = asyncio.create_task(self.update_market_data()) + self.optimization_task = asyncio.create_task(self.run_optimization_cycle()) + + logger.info(f"Mock optimization server started") + + async def stop(self): + """Stop the mock optimization server""" + self.running = False + + if hasattr(self, 'market_task'): + self.market_task.cancel() + if hasattr(self, 'optimization_task'): + self.optimization_task.cancel() + + logger.info("Mock optimization server stopped") + + +async def main(): + """Main function to run the mock optimization server""" + server = MockOptimizationServer() + + try: + await server.start() + + # Keep server running + while True: + await asyncio.sleep(1) + + except KeyboardInterrupt: + print("\nShutting down mock optimization server...") + await server.stop() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/mock-scada-server.py b/mock-scada-server.py new file mode 100755 index 0000000..c806b17 --- /dev/null +++ b/mock-scada-server.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +""" +Mock SCADA Server for Testing +Simulates a real SCADA system with OPC UA and Modbus interfaces +""" + +import asyncio +import logging +import random +import time +from datetime import datetime +from typing import Dict, Any + +# OPC UA imports +try: + from asyncua import Server, Node + OPCUA_AVAILABLE = True +except ImportError: + OPCUA_AVAILABLE = False + print("Warning: asyncua not available. Install with: pip install asyncua") + +# Modbus imports +try: + from pymodbus.server import StartTcpServer + from pymodbus.datastore import ModbusSequentialDataBlock + from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext + MODBUS_AVAILABLE = True +except ImportError: + MODBUS_AVAILABLE = False + print("Warning: pymodbus not available. Install with: pip install pymodbus") + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class MockSCADAServer: + """Mock SCADA server that simulates industrial control systems""" + + def __init__(self): + self.opcua_server = None + self.modbus_server = None + self.running = False + + # Simulated process data + self.process_data = { + 'temperature': 25.0, + 'pressure': 101.3, + 'flow_rate': 100.0, + 'level': 50.0, + 'valve_position': 75.0, + 'pump_status': True, + 'alarm_status': False, + 'setpoint': 100.0 + } + + # Historical data for trends + self.historical_data = { + 'temperature': [], + 'pressure': [], + 'flow_rate': [] + } + + async def start_opcua_server(self, endpoint: str = "opc.tcp://0.0.0.0:4840"): + """Start OPC UA server""" + if not OPCUA_AVAILABLE: + logger.warning("OPC UA not available, skipping OPC UA server") + return + + try: + self.opcua_server = Server() + await self.opcua_server.init() + + self.opcua_server.set_endpoint(endpoint) + self.opcua_server.set_server_name("Mock SCADA Server") + + # Setup namespace + uri = "http://mock-scada.org" + idx = await self.opcua_server.register_namespace(uri) + + # Create object node + objects = self.opcua_server.get_objects_node() + scada_object = await objects.add_object(idx, "SCADA_System") + + # Add process variables + self.opcua_nodes = {} + for name, value in self.process_data.items(): + if isinstance(value, bool): + node = await scada_object.add_variable(idx, name, value) + elif isinstance(value, (int, float)): + node = await scada_object.add_variable(idx, name, float(value)) + else: + continue + await node.set_writable() + self.opcua_nodes[name] = node + + await self.opcua_server.start() + logger.info(f"Mock OPC UA server started at {endpoint}") + + except Exception as e: + logger.error(f"Failed to start OPC UA server: {e}") + + def start_modbus_server(self, port: int = 502): + """Start Modbus TCP server""" + if not MODBUS_AVAILABLE: + logger.warning("Modbus not available, skipping Modbus server") + return + + try: + # Create data blocks + store = ModbusSlaveContext( + di=ModbusSequentialDataBlock(0, [0]*100), # Discrete Inputs + co=ModbusSequentialDataBlock(0, [0]*100), # Coils + hr=ModbusSequentialDataBlock(0, [0]*100), # Holding Registers + ir=ModbusSequentialDataBlock(0, [0]*100) # Input Registers + ) + + context = ModbusServerContext(slaves=store, single=True) + + # Start server in background thread + import threading + def run_modbus_server(): + StartTcpServer(context=context, address=("0.0.0.0", port)) + + modbus_thread = threading.Thread(target=run_modbus_server, daemon=True) + modbus_thread.start() + + self.modbus_server = modbus_thread + logger.info(f"Mock Modbus server started on port {port}") + + except Exception as e: + logger.error(f"Failed to start Modbus server: {e}") + + async def update_process_data(self): + """Update simulated process data""" + while self.running: + try: + # Simulate realistic process variations + self.process_data['temperature'] += random.uniform(-0.5, 0.5) + self.process_data['temperature'] = max(20.0, min(80.0, self.process_data['temperature'])) + + self.process_data['pressure'] += random.uniform(-0.1, 0.1) + self.process_data['pressure'] = max(95.0, min(110.0, self.process_data['pressure'])) + + self.process_data['flow_rate'] += random.uniform(-2.0, 2.0) + self.process_data['flow_rate'] = max(0.0, min(200.0, self.process_data['flow_rate'])) + + self.process_data['level'] += random.uniform(-1.0, 1.0) + self.process_data['level'] = max(0.0, min(100.0, self.process_data['level'])) + + # Simulate valve and pump behavior + if self.process_data['flow_rate'] > 150: + self.process_data['valve_position'] = max(0, self.process_data['valve_position'] - 1) + elif self.process_data['flow_rate'] < 50: + self.process_data['valve_position'] = min(100, self.process_data['valve_position'] + 1) + + # Simulate alarms + self.process_data['alarm_status'] = ( + self.process_data['temperature'] > 75.0 or + self.process_data['pressure'] > 108.0 or + self.process_data['level'] > 95.0 + ) + + # Update OPC UA nodes if available + if self.opcua_server and self.opcua_nodes: + for name, node in self.opcua_nodes.items(): + await node.write_value(self.process_data[name]) + + # Store historical data + timestamp = datetime.now() + self.historical_data['temperature'].append({ + 'timestamp': timestamp, + 'value': self.process_data['temperature'] + }) + self.historical_data['pressure'].append({ + 'timestamp': timestamp, + 'value': self.process_data['pressure'] + }) + self.historical_data['flow_rate'].append({ + 'timestamp': timestamp, + 'value': self.process_data['flow_rate'] + }) + + # Keep only last 1000 points + for key in self.historical_data: + if len(self.historical_data[key]) > 1000: + self.historical_data[key] = self.historical_data[key][-1000:] + + await asyncio.sleep(1) # Update every second + + except Exception as e: + logger.error(f"Error updating process data: {e}") + await asyncio.sleep(5) + + def get_status(self) -> Dict[str, Any]: + """Get server status""" + return { + 'running': self.running, + 'opcua_available': OPCUA_AVAILABLE and self.opcua_server is not None, + 'modbus_available': MODBUS_AVAILABLE and self.modbus_server is not None, + 'process_data': self.process_data, + 'data_points': sum(len(data) for data in self.historical_data.values()) + } + + async def start(self): + """Start the mock SCADA server""" + if self.running: + return + + self.running = True + + # Start servers + await self.start_opcua_server() + self.start_modbus_server() + + # Start data update loop + self.update_task = asyncio.create_task(self.update_process_data()) + + logger.info("Mock SCADA server started") + + async def stop(self): + """Stop the mock SCADA server""" + self.running = False + + if hasattr(self, 'update_task'): + self.update_task.cancel() + try: + await self.update_task + except asyncio.CancelledError: + pass + + if self.opcua_server: + await self.opcua_server.stop() + + logger.info("Mock SCADA server stopped") + + +async def main(): + """Main function to run the mock SCADA server""" + server = MockSCADAServer() + + try: + await server.start() + + # Keep server running + while True: + await asyncio.sleep(1) + + except KeyboardInterrupt: + print("\nShutting down mock SCADA server...") + await server.stop() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/test-e2e-deployment.py b/test-e2e-deployment.py new file mode 100755 index 0000000..1a3e955 --- /dev/null +++ b/test-e2e-deployment.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +""" +End-to-End Deployment Testing Script +Tests the complete Calejo Control Adapter system with mock SCADA and optimization +""" + +import asyncio +import time +import requests +import json +import logging +import subprocess +import sys +from pathlib import Path +from typing import Dict, Any, List + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + + +class E2ETestRunner: + """End-to-end test runner for Calejo Control Adapter""" + + def __init__(self): + self.base_url = "http://localhost:8080" + self.mock_scada_process = None + self.mock_optimization_process = None + self.main_app_process = None + + # Test results + self.test_results = [] + + def start_mock_servers(self): + """Start mock SCADA and optimization servers""" + logger.info("Starting mock servers...") + + try: + # Start mock SCADA server + self.mock_scada_process = subprocess.Popen( + [sys.executable, "mock-scada-server.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # Start mock optimization server + self.mock_optimization_process = subprocess.Popen( + [sys.executable, "mock-optimization-server.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # Wait for servers to start + time.sleep(5) + logger.info("Mock servers started") + return True + + except Exception as e: + logger.error(f"Failed to start mock servers: {e}") + return False + + def start_main_application(self): + """Start the main Calejo Control Adapter application""" + logger.info("Starting main application...") + + try: + self.main_app_process = subprocess.Popen( + [sys.executable, "src/main.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # Wait for application to start + for i in range(30): + try: + response = requests.get(f"{self.base_url}/health", timeout=2) + if response.status_code == 200: + logger.info("Main application started") + return True + except: + pass + + time.sleep(2) + if i % 5 == 0: + logger.info(f"Waiting for application to start... ({i*2}s)") + + logger.error("Main application failed to start within 60 seconds") + return False + + except Exception as e: + logger.error(f"Failed to start main application: {e}") + return False + + def stop_servers(self): + """Stop all running servers""" + logger.info("Stopping servers...") + + if self.main_app_process: + self.main_app_process.terminate() + self.main_app_process.wait(timeout=10) + + if self.mock_scada_process: + self.mock_scada_process.terminate() + self.mock_scada_process.wait(timeout=10) + + if self.mock_optimization_process: + self.mock_optimization_process.terminate() + self.mock_optimization_process.wait(timeout=10) + + logger.info("All servers stopped") + + def test_health_endpoint(self) -> bool: + """Test health endpoint""" + logger.info("Testing health endpoint...") + + try: + response = requests.get(f"{self.base_url}/health") + if response.status_code == 200: + data = response.json() + logger.info(f"Health status: {data.get('status', 'unknown')}") + self.test_results.append(("Health Endpoint", True, "Health check successful")) + return True + else: + logger.error(f"Health endpoint returned {response.status_code}") + self.test_results.append(("Health Endpoint", False, f"HTTP {response.status_code}")) + return False + + except Exception as e: + logger.error(f"Health endpoint test failed: {e}") + self.test_results.append(("Health Endpoint", False, str(e))) + return False + + def test_dashboard_access(self) -> bool: + """Test dashboard access""" + logger.info("Testing dashboard access...") + + try: + response = requests.get(f"{self.base_url}/dashboard") + if response.status_code == 200: + if "Calejo Control Adapter Dashboard" in response.text: + logger.info("Dashboard HTML loaded successfully") + self.test_results.append(("Dashboard Access", True, "Dashboard loaded")) + return True + else: + logger.error("Dashboard HTML content incorrect") + self.test_results.append(("Dashboard Access", False, "Incorrect content")) + return False + else: + logger.error(f"Dashboard returned {response.status_code}") + self.test_results.append(("Dashboard Access", False, f"HTTP {response.status_code}")) + return False + + except Exception as e: + logger.error(f"Dashboard access test failed: {e}") + self.test_results.append(("Dashboard Access", False, str(e))) + return False + + def test_dashboard_api(self) -> bool: + """Test dashboard API endpoints""" + logger.info("Testing dashboard API...") + + endpoints = [ + ("/api/v1/dashboard/status", "Status API"), + ("/api/v1/dashboard/config", "Config API"), + ("/api/v1/dashboard/logs", "Logs API"), + ("/api/v1/dashboard/actions", "Actions API") + ] + + all_passed = True + + for endpoint, name in endpoints: + try: + response = requests.get(f"{self.base_url}{endpoint}") + if response.status_code == 200: + logger.info(f"{name}: OK") + self.test_results.append((f"{name}", True, "API accessible")) + else: + logger.error(f"{name}: HTTP {response.status_code}") + self.test_results.append((f"{name}", False, f"HTTP {response.status_code}")) + all_passed = False + + except Exception as e: + logger.error(f"{name} test failed: {e}") + self.test_results.append((f"{name}", False, str(e))) + all_passed = False + + return all_passed + + def test_configuration_management(self) -> bool: + """Test configuration management""" + logger.info("Testing configuration management...") + + try: + # Get current configuration + response = requests.get(f"{self.base_url}/api/v1/dashboard/config") + if response.status_code != 200: + logger.error("Failed to get configuration") + self.test_results.append(("Configuration Management", False, "Get config failed")) + return False + + config = response.json() + + # Test configuration update + test_config = { + "database": { + "host": "test-host", + "port": 5432, + "name": "test-db", + "user": "test-user", + "password": "test-pass" + }, + "opcua": { + "enabled": True, + "port": 4840 + }, + "modbus": { + "enabled": True, + "port": 502 + }, + "rest_api": { + "enabled": True, + "port": 8080 + }, + "monitoring": { + "enabled": True, + "port": 9090 + }, + "security": { + "enable_auth": False, + "enable_ssl": False + } + } + + response = requests.post( + f"{self.base_url}/api/v1/dashboard/config", + json=test_config + ) + + if response.status_code == 200: + result = response.json() + if result.get("success", False): + logger.info("Configuration update successful") + self.test_results.append(("Configuration Management", True, "Config update successful")) + return True + else: + logger.error(f"Configuration update failed: {result.get('error', 'Unknown error')}") + self.test_results.append(("Configuration Management", False, result.get('error', 'Unknown error'))) + return False + else: + logger.error(f"Configuration update returned {response.status_code}") + self.test_results.append(("Configuration Management", False, f"HTTP {response.status_code}")) + return False + + except Exception as e: + logger.error(f"Configuration management test failed: {e}") + self.test_results.append(("Configuration Management", False, str(e))) + return False + + def test_system_actions(self) -> bool: + """Test system actions""" + logger.info("Testing system actions...") + + try: + # Test health check action + response = requests.post( + f"{self.base_url}/api/v1/dashboard/actions", + json={"action": "health_check"} + ) + + if response.status_code == 200: + result = response.json() + if result.get("success", False): + logger.info("Health check action successful") + self.test_results.append(("System Actions", True, "Health check successful")) + return True + else: + logger.error(f"Health check failed: {result.get('error', 'Unknown error')}") + self.test_results.append(("System Actions", False, result.get('error', 'Unknown error'))) + return False + else: + logger.error(f"Health check action returned {response.status_code}") + self.test_results.append(("System Actions", False, f"HTTP {response.status_code}")) + return False + + except Exception as e: + logger.error(f"System actions test failed: {e}") + self.test_results.append(("System Actions", False, str(e))) + return False + + def test_integration_with_mock_servers(self) -> bool: + """Test integration with mock SCADA and optimization servers""" + logger.info("Testing integration with mock servers...") + + try: + # Test if mock SCADA is responding (OPC UA would require specific client) + # For now, just check if processes are running + if (self.mock_scada_process and self.mock_scada_process.poll() is None and + self.mock_optimization_process and self.mock_optimization_process.poll() is None): + logger.info("Mock servers are running") + self.test_results.append(("Mock Server Integration", True, "Mock servers running")) + return True + else: + logger.error("Mock servers are not running") + self.test_results.append(("Mock Server Integration", False, "Mock servers not running")) + return False + + except Exception as e: + logger.error(f"Integration test failed: {e}") + self.test_results.append(("Mock Server Integration", False, str(e))) + return False + + def run_all_tests(self) -> bool: + """Run all end-to-end tests""" + logger.info("Starting end-to-end deployment tests...") + + # Start servers + if not self.start_mock_servers(): + logger.error("Failed to start mock servers") + return False + + if not self.start_main_application(): + logger.error("Failed to start main application") + self.stop_servers() + return False + + # Run tests + tests = [ + self.test_health_endpoint, + self.test_dashboard_access, + self.test_dashboard_api, + self.test_configuration_management, + self.test_system_actions, + self.test_integration_with_mock_servers + ] + + all_passed = True + for test_func in tests: + if not test_func(): + all_passed = False + + # Stop servers + self.stop_servers() + + # Print results + self.print_test_results() + + return all_passed + + def print_test_results(self): + """Print test results summary""" + print("\n" + "="*60) + print("END-TO-END DEPLOYMENT TEST RESULTS") + print("="*60) + + passed = 0 + total = len(self.test_results) + + for test_name, success, message in self.test_results: + status = "โœ… PASS" if success else "โŒ FAIL" + print(f"{status} {test_name}: {message}") + if success: + passed += 1 + + print("\n" + "="*60) + print(f"SUMMARY: {passed}/{total} tests passed") + + if passed == total: + print("๐ŸŽ‰ SUCCESS: All end-to-end tests passed!") + print("The deployment is ready for production use.") + else: + print("โŒ Some tests failed. Please check the deployment.") + print("="*60) + + +def main(): + """Main function""" + print("๐Ÿš€ Calejo Control Adapter - End-to-End Deployment Test") + print("="*60) + + # Check if required files exist + required_files = ["mock-scada-server.py", "mock-optimization-server.py", "src/main.py"] + for file in required_files: + if not Path(file).exists(): + print(f"โŒ Required file not found: {file}") + return 1 + + # Run tests + test_runner = E2ETestRunner() + success = test_runner.run_all_tests() + + return 0 if success else 1 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/validate-deployment.sh b/validate-deployment.sh new file mode 100755 index 0000000..12362a3 --- /dev/null +++ b/validate-deployment.sh @@ -0,0 +1,353 @@ +#!/bin/bash + +# Calejo Control Adapter - Deployment Validation Script +# Validates that the deployment is healthy and ready for production + +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 + +# Configuration +BASE_URL="http://localhost:8080" +DEPLOYMENT_DIR="/opt/calejo-control-adapter" + +# 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 check service health +check_service_health() { + local service_name=$1 + local port=$2 + local endpoint=$3 + + if curl -s -f "http://localhost:$port$endpoint" > /dev/null; then + print_success "$service_name is healthy (port $port)" + return 0 + else + print_error "$service_name is not responding (port $port)" + return 1 + fi +} + +# Function to check container status +check_container_status() { + print_status "Checking Docker container status..." + + if command -v docker-compose > /dev/null && [ -f "docker-compose.yml" ]; then + cd $DEPLOYMENT_DIR + + if docker-compose ps | grep -q "Up"; then + print_success "All Docker containers are running" + docker-compose ps --format "table {{.Service}}\t{{.State}}\t{{.Ports}}" + return 0 + else + print_error "Some Docker containers are not running" + docker-compose ps + return 1 + fi + else + print_warning "Docker Compose not available or docker-compose.yml not found" + return 0 + fi +} + +# Function to check system resources +check_system_resources() { + print_status "Checking system resources..." + + # Check disk space + local disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') + if [ $disk_usage -gt 90 ]; then + print_error "Disk usage is high: ${disk_usage}%" + elif [ $disk_usage -gt 80 ]; then + print_warning "Disk usage is moderate: ${disk_usage}%" + else + print_success "Disk usage is normal: ${disk_usage}%" + fi + + # Check memory + local mem_info=$(free -h) + print_status "Memory usage:" + echo "$mem_info" | head -2 + + # Check CPU load + local load_avg=$(cat /proc/loadavg | awk '{print $1}') + local cpu_cores=$(nproc) + local load_percent=$(echo "scale=0; $load_avg * 100 / $cpu_cores" | bc) + + if [ $load_percent -gt 90 ]; then + print_error "CPU load is high: ${load_avg} (${load_percent}% of capacity)" + elif [ $load_percent -gt 70 ]; then + print_warning "CPU load is moderate: ${load_avg} (${load_percent}% of capacity)" + else + print_success "CPU load is normal: ${load_avg} (${load_percent}% of capacity)" + fi +} + +# Function to check application endpoints +check_application_endpoints() { + print_status "Checking application endpoints..." + + endpoints=( + "/health" + "/dashboard" + "/api/v1/status" + "/api/v1/dashboard/status" + "/api/v1/dashboard/config" + "/api/v1/dashboard/logs" + "/api/v1/dashboard/actions" + ) + + all_healthy=true + + for endpoint in "${endpoints[@]}"; do + if curl -s -f "$BASE_URL$endpoint" > /dev/null; then + print_success "Endpoint $endpoint is accessible" + else + print_error "Endpoint $endpoint is not accessible" + all_healthy=false + fi + done + + if $all_healthy; then + print_success "All application endpoints are accessible" + return 0 + else + print_error "Some application endpoints are not accessible" + return 1 + fi +} + +# Function to check configuration +check_configuration() { + print_status "Checking configuration..." + + # Check if configuration files exist + config_files=( + "$DEPLOYMENT_DIR/config/settings.py" + "$DEPLOYMENT_DIR/docker-compose.yml" + "$CONFIG_DIR/settings.py" + ) + + for config_file in "${config_files[@]}"; do + if [ -f "$config_file" ]; then + print_success "Configuration file exists: $config_file" + else + print_warning "Configuration file missing: $config_file" + fi + done + + # Check if configuration is valid + if curl -s "$BASE_URL/api/v1/dashboard/config" | grep -q '"success":true'; then + print_success "Configuration is valid and accessible" + return 0 + else + print_error "Configuration validation failed" + return 1 + fi +} + +# Function to check logs +check_logs() { + print_status "Checking logs..." + + log_dirs=( + "/var/log/calejo" + "$DEPLOYMENT_DIR/logs" + ) + + for log_dir in "${log_dirs[@]}"; do + if [ -d "$log_dir" ]; then + local log_count=$(find "$log_dir" -name "*.log" -type f | wc -l) + if [ $log_count -gt 0 ]; then + print_success "Log directory contains $log_count log files: $log_dir" + + # Check for recent errors + local error_count=$(find "$log_dir" -name "*.log" -type f -exec grep -l -i "error\|exception\|fail" {} \; | wc -l) + if [ $error_count -gt 0 ]; then + print_warning "Found $error_count log files with errors" + fi + else + print_warning "Log directory exists but contains no log files: $log_dir" + fi + else + print_warning "Log directory does not exist: $log_dir" + fi + done +} + +# Function to check security +check_security() { + print_status "Checking security configuration..." + + # Check for default credentials warning + if curl -s "$BASE_URL/api/v1/dashboard/config" | grep -q '"security_warning":true'; then + print_warning "Security warning: Default credentials detected" + else + print_success "No security warnings detected" + fi + + # Check if ports are properly exposed + local open_ports=$(ss -tuln | grep -E ":(8080|4840|502|9090)" | wc -l) + if [ $open_ports -gt 0 ]; then + print_success "Required ports are open" + else + print_warning "Some required ports may not be open" + fi +} + +# Function to check backup configuration +check_backup_configuration() { + print_status "Checking backup configuration..." + + if [ -f "$DEPLOYMENT_DIR/scripts/backup-full.sh" ]; then + print_success "Backup script exists: $DEPLOYMENT_DIR/scripts/backup-full.sh" + + # Check if backup directory exists and is writable + if [ -w "/var/backup/calejo" ]; then + print_success "Backup directory is writable: /var/backup/calejo" + else + print_error "Backup directory is not writable: /var/backup/calejo" + fi + else + print_error "Backup script not found" + fi +} + +# Function to generate validation report +generate_validation_report() { + print_status "Generating validation report..." + + local report_file="/tmp/calejo-deployment-validation-$(date +%Y%m%d_%H%M%S).txt" + + cat > "$report_file" << EOF +Calejo Control Adapter - Deployment Validation Report +Generated: $(date) +System: $(hostname) + +VALIDATION CHECKS: +EOF + + # Run checks and capture output + { + echo "1. System Resources:" + check_system_resources 2>&1 | sed 's/^/ /' + echo "" + + echo "2. Container Status:" + check_container_status 2>&1 | sed 's/^/ /' + echo "" + + echo "3. Application Endpoints:" + check_application_endpoints 2>&1 | sed 's/^/ /' + echo "" + + echo "4. Configuration:" + check_configuration 2>&1 | sed 's/^/ /' + echo "" + + echo "5. Logs:" + check_logs 2>&1 | sed 's/^/ /' + echo "" + + echo "6. Security:" + check_security 2>&1 | sed 's/^/ /' + echo "" + + echo "7. Backup Configuration:" + check_backup_configuration 2>&1 | sed 's/^/ /' + echo "" + + echo "SUMMARY:" + echo "Deployment validation completed. Review any warnings or errors above." + + } >> "$report_file" + + print_success "Validation report generated: $report_file" + + # Display summary + echo "" + echo "==================================================" + echo " DEPLOYMENT VALIDATION SUMMARY" + echo "==================================================" + echo "" + echo "๐Ÿ“Š System Status:" + check_system_resources | grep -E "(Disk usage|CPU load)" + echo "" + echo "๐Ÿ”ง Application Status:" + check_application_endpoints > /dev/null 2>&1 && echo " โœ… All endpoints accessible" || echo " โŒ Some endpoints failed" + echo "" + echo "๐Ÿ“‹ Next Steps:" + echo " Review full report: $report_file" + echo " Address any warnings or errors" + echo " Run end-to-end tests: python test-e2e-deployment.py" + echo "" + echo "==================================================" +} + +# Main validation function +main() { + echo "" + echo "๐Ÿ” Calejo Control Adapter - Deployment Validation" + echo "==================================================" + echo "" + + # Check if application is running + if ! curl -s "$BASE_URL/health" > /dev/null 2>&1; then + print_error "Application is not running or not accessible at $BASE_URL" + echo "" + echo "Please ensure the application is running before validation." + echo "Start with: systemctl start calejo-control-adapter" + exit 1 + fi + + # Run validation checks + check_system_resources + echo "" + + check_container_status + echo "" + + check_application_endpoints + echo "" + + check_configuration + echo "" + + check_logs + echo "" + + check_security + echo "" + + check_backup_configuration + echo "" + + # Generate comprehensive report + generate_validation_report + + echo "" + print_success "Deployment validation completed!" +} + +# Run main function +main "$@" \ No newline at end of file