🎉 Feature: Implement MASSIVE KONSOLIDIERUNG PLAN in backend utils

This commit is contained in:
2025-06-11 13:00:30 +02:00
parent 6961732fc8
commit 85cd38830a
5 changed files with 1332 additions and 1100 deletions

View File

@ -0,0 +1,822 @@
#!/usr/bin/env python3.11
"""
Core System Management - Massive Konsolidierung
==============================================
Konsolidiert alle System-Management-Funktionalitäten in einer Datei:
- System Control (system_control.py)
- Shutdown Manager (shutdown_manager.py)
- Watchdog Manager (watchdog_manager.py)
- Windows Fixes (windows_fixes.py)
- Error Recovery (error_recovery.py)
- Timeout Force Quit Manager (timeout_force_quit_manager.py)
Migration: 6 Dateien → 1 Datei
Autor: MYP Team - Massive Konsolidierung für IHK-Projektarbeit
Datum: 2025-06-09
"""
import os
import sys
import time
import signal
import threading
import subprocess
import platform
import traceback
import shutil
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
from dataclasses import dataclass
from enum import Enum
from contextlib import contextmanager
from pathlib import Path
from utils.logging_config import get_logger
# ===== UNIFIED LOGGER =====
core_logger = get_logger("core_system")
# ===== ENUMS =====
class SystemOperation(Enum):
"""Verfügbare System-Operationen"""
RESTART = "restart"
SHUTDOWN = "shutdown"
KIOSK_RESTART = "kiosk_restart"
KIOSK_ENABLE = "kiosk_enable"
KIOSK_DISABLE = "kiosk_disable"
SERVICE_RESTART = "service_restart"
EMERGENCY_STOP = "emergency_stop"
class ErrorSeverity(Enum):
"""Schweregrade von Fehlern"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class RecoveryAction(Enum):
"""Verfügbare Recovery-Aktionen"""
LOG_ONLY = "log_only"
RESTART_SERVICE = "restart_service"
RESTART_COMPONENT = "restart_component"
CLEAR_CACHE = "clear_cache"
RESET_DATABASE = "reset_database"
RESTART_SYSTEM = "restart_system"
EMERGENCY_STOP = "emergency_stop"
# ===== DATA CLASSES =====
@dataclass
class ErrorPattern:
"""Definiert ein Fehlermuster und zugehörige Recovery-Aktionen"""
name: str
patterns: List[str] # Regex-Patterns
severity: ErrorSeverity
actions: List[RecoveryAction]
max_occurrences: int = 3
time_window: int = 300 # Sekunden
escalation_actions: List[RecoveryAction] = None
description: str = ""
@dataclass
class ErrorOccurrence:
"""Einzelnes Auftreten eines Fehlers"""
timestamp: datetime
pattern_name: str
error_message: str
severity: ErrorSeverity
context: Dict[str, Any] = None
recovery_attempted: List[RecoveryAction] = None
recovery_successful: bool = False
# ===== CORE SYSTEM MANAGER =====
class CoreSystemManager:
"""
Zentraler System-Manager für alle kritischen System-Operationen.
Konsolidiert System Control, Shutdown Management, Error Recovery und Windows-Fixes.
"""
def __init__(self, timeout: int = 30):
self.timeout = timeout
self.shutdown_requested = False
self.shutdown_time = None
self.components = {}
self.cleanup_functions = []
self.pending_operations = {}
self.operation_history = []
# Error Recovery
self.error_patterns = {}
self.error_occurrences = []
self.recovery_handlers = {}
self.monitoring_active = False
self.monitoring_thread = None
# Windows Thread Management
self.registered_threads = []
self.cleanup_callbacks = []
self._init_default_patterns()
self._init_recovery_handlers()
self._register_signal_handlers()
self._apply_platform_fixes()
core_logger.info("🚀 Core System Manager initialisiert")
# ===== SYSTEM CONTROL =====
def is_safe_to_operate(self) -> Tuple[bool, str]:
"""Prüft, ob es sicher ist, System-Operationen durchzuführen"""
try:
from models import get_cached_session, Job
with get_cached_session() as session:
# Aktive Jobs prüfen
active_jobs = session.query(Job).filter(
Job.status.in_(['printing', 'paused'])
).count()
if active_jobs > 0:
return False, f"{active_jobs} aktive Jobs laufen noch"
# System-Load prüfen
if hasattr(os, 'getloadavg'):
load = os.getloadavg()[0]
if load > 2.0:
return False, f"System-Load zu hoch: {load:.1f}"
return True, "System bereit für Operationen"
except Exception as e:
return False, f"Sicherheitsprüfung fehlgeschlagen: {str(e)}"
def schedule_operation(self, operation: SystemOperation, delay_seconds: int = None,
user_id: str = None, reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Plant eine System-Operation"""
operation_id = f"{operation.value}_{int(time.time())}"
if not force:
safe, message = self.is_safe_to_operate()
if not safe:
return {
'success': False,
'message': f"Operation nicht sicher: {message}",
'operation_id': None
}
operation_data = {
'id': operation_id,
'operation': operation,
'scheduled_at': datetime.now(),
'execute_at': datetime.now() + timedelta(seconds=delay_seconds or 5),
'user_id': user_id,
'reason': reason or f"Geplante {operation.value}",
'force': force,
'status': 'scheduled'
}
self.pending_operations[operation_id] = operation_data
if delay_seconds and delay_seconds > 0:
# Verzögerte Ausführung
timer = threading.Timer(delay_seconds, self._execute_delayed_operation, [operation_id])
timer.daemon = True
timer.start()
operation_data['timer'] = timer
else:
# Sofortige Ausführung
self._execute_delayed_operation(operation_id)
core_logger.info(f"System-Operation geplant: {operation.value} (ID: {operation_id})")
return {
'success': True,
'message': f"Operation {operation.value} geplant",
'operation_id': operation_id,
'execute_at': operation_data['execute_at'].isoformat()
}
def _execute_delayed_operation(self, operation_id: str):
"""Führt eine verzögerte Operation aus"""
if operation_id not in self.pending_operations:
return
operation_data = self.pending_operations[operation_id]
operation = operation_data['operation']
try:
operation_data['status'] = 'executing'
operation_data['started_at'] = datetime.now()
core_logger.info(f"Führe System-Operation aus: {operation.value}")
result = self._execute_operation(operation, operation_data)
operation_data.update(result)
except Exception as e:
operation_data['status'] = 'failed'
operation_data['error'] = str(e)
core_logger.error(f"System-Operation fehlgeschlagen: {operation.value} - {str(e)}")
finally:
operation_data['completed_at'] = datetime.now()
self._move_to_history(operation_id)
def _execute_operation(self, operation: SystemOperation, operation_data: Dict) -> Dict[str, Any]:
"""Führt die eigentliche System-Operation aus"""
if operation == SystemOperation.RESTART:
return self._restart_system(operation_data)
elif operation == SystemOperation.SHUTDOWN:
return self._shutdown_system(operation_data)
elif operation == SystemOperation.KIOSK_RESTART:
return self._restart_kiosk(operation_data)
elif operation == SystemOperation.EMERGENCY_STOP:
return self._emergency_stop(operation_data)
else:
return {'status': 'failed', 'message': f'Unbekannte Operation: {operation.value}'}
def _restart_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet das System neu"""
try:
self._cleanup_before_restart()
core_logger.info("System-Neustart wird eingeleitet...")
if platform.system() == "Windows":
subprocess.run(['shutdown', '/r', '/t', '5'], check=True)
else:
subprocess.run(['sudo', 'reboot'], check=True)
return {'status': 'success', 'message': 'System-Neustart eingeleitet'}
except Exception as e:
return {'status': 'failed', 'message': f'Neustart fehlgeschlagen: {str(e)}'}
def _shutdown_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Fährt das System herunter"""
try:
self._cleanup_before_restart()
core_logger.info("System-Herunterfahren wird eingeleitet...")
if platform.system() == "Windows":
subprocess.run(['shutdown', '/s', '/t', '5'], check=True)
else:
subprocess.run(['sudo', 'shutdown', 'now'], check=True)
return {'status': 'success', 'message': 'System-Herunterfahren eingeleitet'}
except Exception as e:
return {'status': 'failed', 'message': f'Herunterfahren fehlgeschlagen: {str(e)}'}
def _emergency_stop(self, operation_data: Dict) -> Dict[str, Any]:
"""Führt einen Notfall-Stop durch"""
try:
core_logger.critical("NOTFALL-STOP eingeleitet!")
self._force_shutdown_all_threads()
self._cleanup_before_restart()
os._exit(1)
except Exception as e:
return {'status': 'failed', 'message': f'Notfall-Stop fehlgeschlagen: {str(e)}'}
def _cleanup_before_restart(self):
"""Bereinigung vor Neustart/Herunterfahren"""
try:
# Database WAL cleanup
from utils.database_core import database_service
database_service.cleanup.perform_wal_checkpoint()
# Clear caches
self._clear_caches()
# Stop all registered components
self._shutdown_components()
core_logger.info("Bereinigung vor Neustart abgeschlossen")
except Exception as e:
core_logger.error(f"Fehler bei Bereinigung: {str(e)}")
def _clear_caches(self):
"""Löscht System-Caches"""
try:
cache_dirs = ['/tmp/myp_cache', '/var/cache/myp']
for cache_dir in cache_dirs:
if os.path.exists(cache_dir):
shutil.rmtree(cache_dir, ignore_errors=True)
except Exception as e:
core_logger.warning(f"Cache-Bereinigung teilweise fehlgeschlagen: {str(e)}")
# ===== SHUTDOWN MANAGEMENT =====
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
"""Registriert eine Komponente für ordnungsgemäßes Herunterfahren"""
self.components[name] = {
'component': component,
'stop_method': stop_method,
'priority': 1
}
core_logger.debug(f"Komponente '{name}' für Shutdown registriert")
def register_cleanup_function(self, func: Callable, name: str, priority: int = 1,
timeout: int = 10, args: tuple = (), kwargs: dict = None):
"""Registriert eine Bereinigungsfunktion"""
self.cleanup_functions.append({
'function': func,
'name': name,
'priority': priority,
'timeout': timeout,
'args': args or (),
'kwargs': kwargs or {}
})
core_logger.debug(f"Cleanup-Funktion '{name}' registriert")
def shutdown(self, exit_code: int = 0):
"""Führt ordnungsgemäßes Herunterfahren durch"""
if self.shutdown_requested:
return
self.shutdown_requested = True
self.shutdown_time = datetime.now()
core_logger.info("🔄 Ordnungsgemäßes Herunterfahren wird eingeleitet...")
self._shutdown_components()
self._execute_cleanup_functions()
core_logger.info("✅ Herunterfahren abgeschlossen")
sys.exit(exit_code)
def _shutdown_components(self):
"""Fährt alle registrierten Komponenten herunter"""
for name, component_info in self.components.items():
try:
component = component_info['component']
stop_method = component_info['stop_method']
if hasattr(component, stop_method):
getattr(component, stop_method)()
core_logger.debug(f"Komponente '{name}' erfolgreich heruntergefahren")
else:
core_logger.warning(f"Komponente '{name}' hat keine '{stop_method}' Methode")
except Exception as e:
core_logger.error(f"Fehler beim Herunterfahren von '{name}': {str(e)}")
def _execute_cleanup_functions(self):
"""Führt alle Cleanup-Funktionen aus"""
# Nach Priorität sortieren
sorted_functions = sorted(self.cleanup_functions, key=lambda x: x['priority'])
for cleanup_info in sorted_functions:
try:
with self.cleanup_timeout(cleanup_info['timeout'], cleanup_info['name']):
cleanup_info['function'](*cleanup_info['args'], **cleanup_info['kwargs'])
core_logger.debug(f"Cleanup '{cleanup_info['name']}' erfolgreich")
except Exception as e:
core_logger.error(f"Cleanup '{cleanup_info['name']}' fehlgeschlagen: {str(e)}")
@contextmanager
def cleanup_timeout(self, timeout: int, operation_name: str):
"""Context Manager für Timeout bei Cleanup-Operationen"""
def timeout_handler(signum, frame):
raise TimeoutError(f"Timeout bei {operation_name}")
if platform.system() != "Windows":
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout)
try:
yield
finally:
if platform.system() != "Windows":
signal.alarm(0)
signal.signal(signal.SIGALRM, old_handler)
# ===== ERROR RECOVERY =====
def _init_default_patterns(self):
"""Initialisiert Standard-Fehlermuster"""
patterns = [
ErrorPattern(
name="database_lock",
patterns=[r"database.*locked", r"sqlite.*busy"],
severity=ErrorSeverity.HIGH,
actions=[RecoveryAction.CLEAR_CACHE, RecoveryAction.RESTART_COMPONENT],
description="Datenbank-Sperren"
),
ErrorPattern(
name="memory_exhaustion",
patterns=[r"out of memory", r"memory.*exhausted"],
severity=ErrorSeverity.CRITICAL,
actions=[RecoveryAction.RESTART_SYSTEM],
description="Speicher-Erschöpfung"
),
ErrorPattern(
name="network_timeout",
patterns=[r"timeout.*network", r"connection.*timed out"],
severity=ErrorSeverity.MEDIUM,
actions=[RecoveryAction.RESTART_SERVICE],
description="Netzwerk-Timeouts"
)
]
for pattern in patterns:
self.error_patterns[pattern.name] = pattern
def _init_recovery_handlers(self):
"""Initialisiert Recovery-Handler"""
self.recovery_handlers = {
RecoveryAction.LOG_ONLY: self._handle_log_only,
RecoveryAction.RESTART_SERVICE: self._handle_restart_service,
RecoveryAction.RESTART_COMPONENT: self._handle_restart_component,
RecoveryAction.CLEAR_CACHE: self._handle_clear_cache,
RecoveryAction.RESET_DATABASE: self._handle_reset_database,
RecoveryAction.RESTART_SYSTEM: self._handle_restart_system,
RecoveryAction.EMERGENCY_STOP: self._handle_emergency_stop
}
def start_error_monitoring(self):
"""Startet Error-Monitoring"""
if self.monitoring_active:
return
self.monitoring_active = True
self.monitoring_thread = threading.Thread(target=self._monitor_loop, daemon=True)
self.monitoring_thread.start()
core_logger.info("Error-Monitoring gestartet")
def stop_error_monitoring(self):
"""Stoppt Error-Monitoring"""
self.monitoring_active = False
if self.monitoring_thread:
self.monitoring_thread.join(timeout=5)
core_logger.info("Error-Monitoring gestoppt")
def _monitor_loop(self):
"""Haupt-Monitoring-Schleife"""
while self.monitoring_active:
try:
self._check_log_files()
self._check_system_metrics()
self._cleanup_old_entries()
time.sleep(30) # Alle 30 Sekunden prüfen
except Exception as e:
core_logger.error(f"Monitoring-Fehler: {str(e)}")
time.sleep(60) # Bei Fehlern länger warten
def _check_log_files(self):
"""Überprüft Log-Dateien auf Fehlermuster"""
try:
from utils.settings import LOG_DIR
if not os.path.exists(LOG_DIR):
return
for log_subdir in os.listdir(LOG_DIR):
log_file = os.path.join(LOG_DIR, log_subdir, f"{log_subdir}.log")
if os.path.exists(log_file):
# Nur die letzten 100 Zeilen lesen
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()[-100:]
for line in lines:
self._analyze_log_line(line, log_subdir)
except Exception as e:
core_logger.debug(f"Log-Datei-Prüfung fehlgeschlagen: {str(e)}")
def _analyze_log_line(self, line: str, source: str):
"""Analysiert eine Log-Zeile auf Fehlermuster"""
import re
for pattern_name, pattern in self.error_patterns.items():
for regex in pattern.patterns:
if re.search(regex, line, re.IGNORECASE):
self._handle_error_detection(pattern_name, line, {'source': source})
return
def _handle_error_detection(self, pattern_name: str, error_message: str, context: Dict[str, Any] = None):
"""Behandelt erkannte Fehler"""
pattern = self.error_patterns[pattern_name]
# Prüfen, ob zu viele Fehler in kurzer Zeit aufgetreten sind
recent_count = self._count_recent_occurrences(pattern_name, pattern.time_window)
occurrence = ErrorOccurrence(
timestamp=datetime.now(),
pattern_name=pattern_name,
error_message=error_message,
severity=pattern.severity,
context=context or {}
)
self.error_occurrences.append(occurrence)
if recent_count >= pattern.max_occurrences:
# Eskalation
actions = pattern.escalation_actions or [RecoveryAction.EMERGENCY_STOP]
core_logger.critical(f"Fehler-Eskalation für {pattern_name}: {recent_count} Vorkommen")
else:
actions = pattern.actions
self._execute_recovery_actions(occurrence, actions)
def _count_recent_occurrences(self, pattern_name: str, time_window: int) -> int:
"""Zählt kürzliche Vorkommen eines Fehlermusters"""
cutoff_time = datetime.now() - timedelta(seconds=time_window)
return len([occ for occ in self.error_occurrences
if occ.pattern_name == pattern_name and occ.timestamp > cutoff_time])
def _execute_recovery_actions(self, occurrence: ErrorOccurrence, actions: List[RecoveryAction]):
"""Führt Recovery-Aktionen aus"""
for action in actions:
if action in self.recovery_handlers:
try:
success = self.recovery_handlers[action](occurrence)
occurrence.recovery_attempted.append(action)
if success:
occurrence.recovery_successful = True
break
except Exception as e:
core_logger.error(f"Recovery-Aktion {action.value} fehlgeschlagen: {str(e)}")
def _handle_log_only(self, occurrence: ErrorOccurrence) -> bool:
"""Recovery: Nur Logging"""
core_logger.warning(f"Fehler erkannt ({occurrence.pattern_name}): {occurrence.error_message}")
return True
def _handle_restart_service(self, occurrence: ErrorOccurrence) -> bool:
"""Recovery: Service-Neustart"""
try:
service_name = occurrence.context.get('service_name', 'myp-https')
subprocess.run(['sudo', 'systemctl', 'restart', service_name], check=True)
core_logger.info(f"Service {service_name} neu gestartet")
return True
except Exception as e:
core_logger.error(f"Service-Neustart fehlgeschlagen: {str(e)}")
return False
def _handle_clear_cache(self, occurrence: ErrorOccurrence) -> bool:
"""Recovery: Cache leeren"""
try:
self._clear_caches()
core_logger.info("Cache erfolgreich geleert")
return True
except Exception as e:
core_logger.error(f"Cache-Bereinigung fehlgeschlagen: {str(e)}")
return False
def _handle_restart_system(self, occurrence: ErrorOccurrence) -> bool:
"""Recovery: System-Neustart"""
try:
self.schedule_operation(SystemOperation.RESTART, delay_seconds=30,
reason=f"Error Recovery: {occurrence.pattern_name}", force=True)
return True
except Exception as e:
core_logger.error(f"System-Neustart fehlgeschlagen: {str(e)}")
return False
def _handle_emergency_stop(self, occurrence: ErrorOccurrence) -> bool:
"""Recovery: Notfall-Stop"""
core_logger.critical(f"NOTFALL-STOP wegen: {occurrence.pattern_name}")
self._emergency_stop({})
return True
# ===== WINDOWS-SPEZIFISCHE FIXES =====
def _apply_platform_fixes(self):
"""Wendet plattformspezifische Fixes an"""
if platform.system() == "Windows":
self._apply_windows_fixes()
else:
self._apply_unix_fixes()
def _apply_windows_fixes(self):
"""Windows-spezifische Fixes"""
try:
# UTF-8 Encoding
import locale
try:
locale.setlocale(locale.LC_ALL, 'German_Germany.1252')
except:
pass
# Socket-Fixes
import socket
if hasattr(socket, 'SO_REUSEADDR'):
original_bind = socket.socket.bind
def windows_bind_with_reuse(self, address):
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return original_bind(self, address)
socket.socket.bind = windows_bind_with_reuse
core_logger.info("Windows-Fixes angewendet")
except Exception as e:
core_logger.warning(f"Windows-Fixes teilweise fehlgeschlagen: {str(e)}")
def _apply_unix_fixes(self):
"""Unix-spezifische Optimierungen"""
try:
# Umgebungsvariablen setzen
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
core_logger.debug("Unix-Optimierungen angewendet")
except Exception as e:
core_logger.warning(f"Unix-Optimierungen fehlgeschlagen: {str(e)}")
# ===== THREAD MANAGEMENT =====
def register_thread(self, thread: threading.Thread):
"""Registriert einen Thread für sauberes Shutdown"""
self.registered_threads.append(thread)
core_logger.debug(f"Thread registriert: {thread.name}")
def register_cleanup_callback(self, func: Callable):
"""Registriert Callback für Cleanup"""
self.cleanup_callbacks.append(func)
def _force_shutdown_all_threads(self):
"""Erzwingt Shutdown aller registrierten Threads"""
for thread in self.registered_threads:
if thread.is_alive():
try:
# Versuche graceful shutdown
if hasattr(thread, 'stop'):
thread.stop()
thread.join(timeout=2)
if thread.is_alive():
core_logger.warning(f"Thread {thread.name} reagiert nicht - erzwinge Beendigung")
except Exception as e:
core_logger.error(f"Fehler beim Thread-Shutdown: {str(e)}")
# Cleanup-Callbacks ausführen
for callback in self.cleanup_callbacks:
try:
callback()
except Exception as e:
core_logger.error(f"Cleanup-Callback fehlgeschlagen: {str(e)}")
# ===== SIGNAL HANDLING =====
def _register_signal_handlers(self):
"""Registriert Signal-Handler für verschiedene Signale"""
if platform.system() != "Windows":
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGHUP, self._signal_handler)
else:
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGINT, self._signal_handler)
def _signal_handler(self, signum, frame):
"""Handler für System-Signale"""
signal_names = {2: 'SIGINT', 15: 'SIGTERM', 1: 'SIGHUP'}
signal_name = signal_names.get(signum, f'Signal {signum}')
core_logger.info(f"📡 {signal_name} empfangen - leite ordnungsgemäßes Herunterfahren ein")
self.shutdown(0)
def _cleanup_old_entries(self):
"""Bereinigt alte Error-Occurrences"""
cutoff_time = datetime.now() - timedelta(hours=24)
self.error_occurrences = [
occ for occ in self.error_occurrences
if occ.timestamp > cutoff_time
]
def _move_to_history(self, operation_id: str):
"""Verschiebt Operation in Historie"""
if operation_id in self.pending_operations:
operation = self.pending_operations.pop(operation_id)
self.operation_history.append(operation)
# Nur die letzten 50 Operationen behalten
self.operation_history = self.operation_history[-50:]
# ===== STATUS & MONITORING =====
def get_system_status(self) -> Dict[str, Any]:
"""Gibt umfassenden System-Status zurück"""
return {
'shutdown_requested': self.shutdown_requested,
'shutdown_time': self.shutdown_time.isoformat() if self.shutdown_time else None,
'registered_components': len(self.components),
'cleanup_functions': len(self.cleanup_functions),
'pending_operations': len(self.pending_operations),
'error_monitoring_active': self.monitoring_active,
'error_patterns': len(self.error_patterns),
'recent_errors': len([occ for occ in self.error_occurrences
if occ.timestamp > datetime.now() - timedelta(hours=1)]),
'registered_threads': len(self.registered_threads),
'platform': platform.system(),
'platform_fixes_applied': True
}
# ===== GLOBALE INSTANZ =====
# Singleton-Pattern für Core System Manager
_core_system_manager = None
def get_core_system_manager(timeout: int = 30) -> CoreSystemManager:
"""Gibt die globale CoreSystemManager-Instanz zurück"""
global _core_system_manager
if _core_system_manager is None:
_core_system_manager = CoreSystemManager(timeout)
return _core_system_manager
# ===== CONVENIENCE FUNCTIONS =====
def schedule_system_restart(delay_seconds: int = 60, user_id: str = None,
reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Convenience-Funktion für System-Neustart"""
manager = get_core_system_manager()
return manager.schedule_operation(SystemOperation.RESTART, delay_seconds, user_id, reason, force)
def schedule_system_shutdown(delay_seconds: int = 30, user_id: str = None,
reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Convenience-Funktion für System-Herunterfahren"""
manager = get_core_system_manager()
return manager.schedule_operation(SystemOperation.SHUTDOWN, delay_seconds, user_id, reason, force)
def register_for_shutdown(component_or_function, name: str,
component_stop_method: str = "stop", priority: int = 1,
timeout: int = 10):
"""Registriert Komponente oder Funktion für Shutdown"""
manager = get_core_system_manager()
if callable(component_or_function):
manager.register_cleanup_function(
component_or_function, name, priority, timeout
)
else:
manager.register_component(name, component_or_function, component_stop_method)
def shutdown_application(exit_code: int = 0):
"""Fährt die Anwendung ordnungsgemäß herunter"""
manager = get_core_system_manager()
manager.shutdown(exit_code)
def start_error_monitoring():
"""Startet Error-Monitoring"""
manager = get_core_system_manager()
manager.start_error_monitoring()
def stop_error_monitoring():
"""Stoppt Error-Monitoring"""
manager = get_core_system_manager()
manager.stop_error_monitoring()
def get_system_status() -> Dict[str, Any]:
"""Gibt System-Status zurück"""
manager = get_core_system_manager()
return manager.get_system_status()
def is_shutdown_requested() -> bool:
"""Prüft, ob Shutdown angefordert wurde"""
manager = get_core_system_manager()
return manager.shutdown_requested
# ===== INITIALIZATION =====
# Auto-Initialisierung beim Import
core_logger.info("🔧 Core System Manager wird initialisiert...")
# Legacy-Kompatibilität: Alte Funktionen automatisch verfügbar machen
def apply_all_windows_fixes():
"""Legacy-Kompatibilität für Windows-Fixes"""
manager = get_core_system_manager()
if platform.system() == "Windows":
manager._apply_windows_fixes()
def fix_windows_socket_issues():
"""Legacy-Kompatibilität für Socket-Fixes"""
apply_all_windows_fixes()
# CLI Interface
if __name__ == "__main__":
if len(sys.argv) > 1:
command = sys.argv[1]
manager = get_core_system_manager()
if command == "status":
status = manager.get_system_status()
print("=== Core System Status ===")
for key, value in status.items():
print(f"{key}: {value}")
elif command == "restart":
result = schedule_system_restart(delay_seconds=10)
print(f"Restart scheduled: {result}")
elif command == "monitor":
print("Starting error monitoring...")
start_error_monitoring()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopping monitoring...")
stop_error_monitoring()
else:
print("Available commands: status, restart, monitor")
else:
print("Core System Manager - Available commands: status, restart, monitor")

View File

@ -0,0 +1,509 @@
#!/usr/bin/env python3.11
"""
Hardware Integration - Konsolidierte Hardware-Steuerung
====================================================
Konsolidiert alle Hardware-Integration-Funktionalitäten:
- TP-Link Tapo Controller (tapo_controller.py)
- Printer Monitor (printer_monitor.py)
Migration: 2 Dateien → 1 Datei
Autor: MYP Team - Konsolidierung für IHK-Projektarbeit
Datum: 2025-06-09
"""
import threading
import time
import socket
import subprocess
import platform
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple
from utils.logging_config import get_logger
# Logger
hardware_logger = get_logger("hardware_integration")
# ===== TAPO SMART PLUG CONTROLLER =====
class TapoController:
"""TP-Link Tapo Smart Plug Controller"""
def __init__(self):
self.default_username = "till.tomczak@mercedes-benz.com"
self.default_password = "744563017196A"
hardware_logger.info("🔌 Tapo Controller initialisiert")
def toggle_plug(self, ip: str, state: bool, username: str = None, password: str = None) -> bool:
"""Schaltet eine Tapo-Steckdose ein oder aus"""
try:
from PyP100 import PyP110
username = username or self.default_username
password = password or self.default_password
p110 = PyP110.P110(ip, username, password)
p110.handshake()
p110.login()
if state:
p110.turnOn()
hardware_logger.info(f"✅ Tapo {ip} eingeschaltet")
else:
p110.turnOff()
hardware_logger.info(f"❌ Tapo {ip} ausgeschaltet")
return True
except Exception as e:
hardware_logger.error(f"Tapo-Steuerung fehlgeschlagen {ip}: {str(e)}")
return False
def check_outlet_status(self, ip: str, username: str = None, password: str = None,
printer_id: int = None) -> Tuple[bool, str]:
"""Prüft den Status einer Tapo-Steckdose"""
try:
from PyP100 import PyP110
username = username or self.default_username
password = password or self.default_password
p110 = PyP110.P110(ip, username, password)
p110.handshake()
p110.login()
device_info = p110.getDeviceInfo()
is_on = device_info.get('device_on', False)
status = "Ein" if is_on else "Aus"
return is_on, status
except Exception as e:
hardware_logger.error(f"Tapo-Status-Prüfung fehlgeschlagen {ip}: {str(e)}")
return False, "Fehler"
# ===== PRINTER MONITOR =====
class PrinterMonitor:
"""3D-Drucker Monitoring System"""
def __init__(self):
self.tapo_controller = TapoController()
self.status_cache = {}
self.cache_timeout = timedelta(minutes=2)
self.last_cache_update = None
hardware_logger.info("🖨️ Printer Monitor initialisiert")
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
"""Holt Live-Status aller Drucker"""
try:
# Cache-Prüfung
if (use_session_cache and self.last_cache_update and
datetime.now() - self.last_cache_update < self.cache_timeout):
return self.status_cache
# Fresh status fetch
status = self._fetch_live_printer_status()
# Cache aktualisieren
self.status_cache = status
self.last_cache_update = datetime.now()
return status
except Exception as e:
hardware_logger.error(f"Live-Status-Abfrage fehlgeschlagen: {str(e)}")
return {}
def _fetch_live_printer_status(self) -> Dict[int, Dict]:
"""Holt tatsächlichen Live-Status"""
try:
from models import get_cached_session, Printer
status_dict = {}
with get_cached_session() as session:
printers = session.query(Printer).filter(Printer.active == True).all()
for printer in printers:
status = self._check_single_printer_status(printer)
status_dict[printer.id] = status
hardware_logger.debug(f"Status für {len(status_dict)} Drucker abgerufen")
return status_dict
except Exception as e:
hardware_logger.error(f"Drucker-Status-Abfrage fehlgeschlagen: {str(e)}")
return {}
def _check_single_printer_status(self, printer) -> Dict:
"""Prüft den Status eines einzelnen Druckers"""
status = {
'printer_id': printer.id,
'name': printer.name,
'ip_address': printer.ip_address,
'plug_ip': printer.plug_ip,
'ping_status': 'unknown',
'outlet_status': 'unknown',
'outlet_on': False,
'last_checked': datetime.now(),
'error_message': None
}
try:
# Ping-Test
if printer.ip_address:
ping_ok = self._ping_address(printer.ip_address)
status['ping_status'] = 'online' if ping_ok else 'offline'
# Outlet-Status prüfen
if printer.plug_ip:
outlet_on, outlet_status = self.tapo_controller.check_outlet_status(
printer.plug_ip, printer.plug_username, printer.plug_password, printer.id
)
status['outlet_on'] = outlet_on
status['outlet_status'] = outlet_status
# Gesamtstatus bestimmen
if status['ping_status'] == 'online' and status['outlet_on']:
status['overall_status'] = 'online'
elif status['outlet_on']:
status['overall_status'] = 'booting'
else:
status['overall_status'] = 'offline'
except Exception as e:
status['error_message'] = str(e)
status['overall_status'] = 'error'
hardware_logger.error(f"Drucker-Status-Prüfung fehlgeschlagen {printer.name}: {str(e)}")
return status
def _ping_address(self, ip_address: str, timeout: int = 3) -> bool:
"""Pingt eine IP-Adresse an"""
try:
if platform.system().lower() == "windows":
result = subprocess.run(
['ping', '-n', '1', '-w', str(timeout * 1000), ip_address],
capture_output=True, text=True, timeout=timeout + 2,
encoding='utf-8', errors='replace'
)
else:
result = subprocess.run(
['ping', '-c', '1', '-W', str(timeout), ip_address],
capture_output=True, text=True, timeout=timeout + 2,
encoding='utf-8', errors='replace'
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
except Exception as e:
hardware_logger.debug(f"Ping fehlgeschlagen {ip_address}: {str(e)}")
return False
def initialize_all_outlets_on_startup(self) -> Dict[str, bool]:
"""Initialisiert alle Outlets beim Systemstart"""
try:
from models import get_cached_session, Printer
results = {}
with get_cached_session() as session:
printers = session.query(Printer).filter(
Printer.active == True,
Printer.plug_ip.isnot(None)
).all()
hardware_logger.info(f"Initialisiere {len(printers)} Drucker-Outlets...")
for printer in printers:
try:
# Outlet ausschalten als Standardzustand
success = self.tapo_controller.toggle_plug(
printer.plug_ip, False,
printer.plug_username, printer.plug_password
)
results[printer.name] = success
if success:
hardware_logger.info(f"{printer.name} Outlet initialisiert (AUS)")
else:
hardware_logger.warning(f"⚠️ {printer.name} Outlet-Initialisierung fehlgeschlagen")
except Exception as e:
results[printer.name] = False
hardware_logger.error(f"{printer.name} Outlet-Fehler: {str(e)}")
success_count = sum(1 for success in results.values() if success)
hardware_logger.info(f"Outlet-Initialisierung: {success_count}/{len(results)} erfolgreich")
return results
except Exception as e:
hardware_logger.error(f"Outlet-Initialisierung fehlgeschlagen: {str(e)}")
return {}
def get_printer_summary(self) -> Dict[str, int]:
"""Gibt eine Zusammenfassung des Drucker-Status zurück"""
try:
status_dict = self.get_live_printer_status()
summary = {
'total': len(status_dict),
'online': 0,
'offline': 0,
'booting': 0,
'error': 0,
'outlets_on': 0,
'outlets_off': 0
}
for printer_status in status_dict.values():
overall_status = printer_status.get('overall_status', 'unknown')
if overall_status in summary:
summary[overall_status] += 1
if printer_status.get('outlet_on'):
summary['outlets_on'] += 1
else:
summary['outlets_off'] += 1
return summary
except Exception as e:
hardware_logger.error(f"Drucker-Zusammenfassung fehlgeschlagen: {str(e)}")
return {'total': 0, 'online': 0, 'offline': 0, 'error': 1}
def auto_discover_tapo_outlets(self) -> Dict[str, bool]:
"""Automatische Erkennung von Tapo-Outlets im Netzwerk"""
try:
discovered = {}
# Standard-IP-Bereiche scannen
base_ips = ["192.168.0.", "192.168.1.", "192.168.100."]
for base_ip in base_ips:
for i in range(100, 110): # Scan 100-109
ip = f"{base_ip}{i}"
try:
# Schneller TCP-Port-Test
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex((ip, 80))
sock.close()
if result == 0:
# Versuche Tapo-Verbindung
is_on, status = self.tapo_controller.check_outlet_status(ip)
if status != "Fehler":
discovered[ip] = True
hardware_logger.info(f"🔍 Tapo-Outlet entdeckt: {ip}")
except Exception:
pass # Ignoriere Fehler beim Scannen
hardware_logger.info(f"Auto-Discovery: {len(discovered)} Tapo-Outlets gefunden")
return discovered
except Exception as e:
hardware_logger.error(f"Auto-Discovery fehlgeschlagen: {str(e)}")
return {}
def clear_all_caches(self):
"""Löscht alle Caches"""
self.status_cache.clear()
self.last_cache_update = None
hardware_logger.info("Alle Hardware-Caches geleert")
# ===== HARDWARE INTEGRATION MANAGER =====
class HardwareIntegrationManager:
"""Zentraler Manager für alle Hardware-Integrationen"""
def __init__(self):
self.tapo_controller = TapoController()
self.printer_monitor = PrinterMonitor()
self.monitoring_active = False
self.monitoring_thread = None
hardware_logger.info("🔧 Hardware Integration Manager initialisiert")
def start_monitoring(self):
"""Startet Hardware-Monitoring"""
if self.monitoring_active:
return
self.monitoring_active = True
self.monitoring_thread = threading.Thread(target=self._monitoring_loop, daemon=True)
self.monitoring_thread.start()
hardware_logger.info("Hardware-Monitoring gestartet")
def stop_monitoring(self):
"""Stoppt Hardware-Monitoring"""
self.monitoring_active = False
if self.monitoring_thread:
self.monitoring_thread.join(timeout=5)
hardware_logger.info("Hardware-Monitoring gestoppt")
def _monitoring_loop(self):
"""Hauptschleife für Hardware-Monitoring"""
while self.monitoring_active:
try:
# Drucker-Status aktualisieren
self.printer_monitor.get_live_printer_status(use_session_cache=False)
# 60 Sekunden warten
for _ in range(60):
if not self.monitoring_active:
break
time.sleep(1)
except Exception as e:
hardware_logger.error(f"Hardware-Monitoring-Fehler: {str(e)}")
time.sleep(30) # Bei Fehlern länger warten
def get_hardware_status(self) -> Dict[str, Any]:
"""Gibt umfassenden Hardware-Status zurück"""
try:
printer_summary = self.printer_monitor.get_printer_summary()
return {
'timestamp': datetime.now().isoformat(),
'monitoring_active': self.monitoring_active,
'printer_summary': printer_summary,
'tapo_controller_active': True,
'cache_status': {
'last_update': self.printer_monitor.last_cache_update.isoformat()
if self.printer_monitor.last_cache_update else None,
'cached_printers': len(self.printer_monitor.status_cache)
}
}
except Exception as e:
hardware_logger.error(f"Hardware-Status-Abfrage fehlgeschlagen: {str(e)}")
return {'error': str(e), 'timestamp': datetime.now().isoformat()}
def initialize_system(self) -> Dict[str, Any]:
"""Initialisiert das gesamte Hardware-System"""
results = {
'outlet_initialization': {},
'auto_discovery': {},
'monitoring_started': False
}
try:
# Outlets initialisieren
results['outlet_initialization'] = self.printer_monitor.initialize_all_outlets_on_startup()
# Auto-Discovery
results['auto_discovery'] = self.printer_monitor.auto_discover_tapo_outlets()
# Monitoring starten
self.start_monitoring()
results['monitoring_started'] = True
hardware_logger.info("Hardware-System erfolgreich initialisiert")
except Exception as e:
hardware_logger.error(f"Hardware-System-Initialisierung fehlgeschlagen: {str(e)}")
results['error'] = str(e)
return results
# ===== GLOBALE INSTANZ =====
# Singleton für Hardware Integration Manager
_hardware_manager = None
def get_hardware_manager() -> HardwareIntegrationManager:
"""Gibt die globale Hardware-Manager-Instanz zurück"""
global _hardware_manager
if _hardware_manager is None:
_hardware_manager = HardwareIntegrationManager()
return _hardware_manager
# ===== CONVENIENCE FUNCTIONS =====
def toggle_plug(ip: str, state: bool, username: str = None, password: str = None) -> bool:
"""Convenience-Funktion für Tapo-Steuerung"""
manager = get_hardware_manager()
return manager.tapo_controller.toggle_plug(ip, state, username, password)
def get_printer_status(printer_id: int = None) -> Dict:
"""Convenience-Funktion für Drucker-Status"""
manager = get_hardware_manager()
all_status = manager.printer_monitor.get_live_printer_status()
if printer_id:
return all_status.get(printer_id, {})
return all_status
def get_hardware_summary() -> Dict[str, Any]:
"""Convenience-Funktion für Hardware-Zusammenfassung"""
manager = get_hardware_manager()
return manager.get_hardware_status()
def initialize_hardware_system() -> Dict[str, Any]:
"""Convenience-Funktion für Hardware-Initialisierung"""
manager = get_hardware_manager()
return manager.initialize_system()
# Legacy-Kompatibilität
def check_outlet_status(ip: str, username: str = None, password: str = None,
printer_id: int = None) -> Tuple[bool, str]:
"""Legacy-Kompatibilität für Outlet-Status"""
manager = get_hardware_manager()
return manager.tapo_controller.check_outlet_status(ip, username, password, printer_id)
def auto_discover_tapo_outlets() -> Dict[str, bool]:
"""Legacy-Kompatibilität für Auto-Discovery"""
manager = get_hardware_manager()
return manager.printer_monitor.auto_discover_tapo_outlets()
def initialize_all_outlets() -> Dict[str, bool]:
"""Legacy-Kompatibilität für Outlet-Initialisierung"""
manager = get_hardware_manager()
return manager.printer_monitor.initialize_all_outlets_on_startup()
# CLI Interface
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
command = sys.argv[1]
manager = get_hardware_manager()
if command == "status":
status = manager.get_hardware_status()
print("=== Hardware Status ===")
for key, value in status.items():
print(f"{key}: {value}")
elif command == "discover":
print("Starte Auto-Discovery...")
discovered = manager.printer_monitor.auto_discover_tapo_outlets()
print(f"Gefundene Outlets: {discovered}")
elif command == "init":
print("Initialisiere Hardware-System...")
results = manager.initialize_system()
print(f"Initialisierung: {results}")
elif command == "monitor":
print("Starte Hardware-Monitoring...")
manager.start_monitoring()
try:
while True:
time.sleep(10)
status = manager.get_hardware_status()
print(f"Status: {status['printer_summary']}")
except KeyboardInterrupt:
print("Stoppe Monitoring...")
manager.stop_monitoring()
else:
print("Available commands: status, discover, init, monitor")
else:
print("Hardware Integration Manager - Available commands: status, discover, init, monitor")

View File

@ -1,442 +0,0 @@
#!/usr/bin/env python3
"""
Zentraler Shutdown-Manager für robuste Anwendungsbeendigung
Koordiniert alle Cleanup-Operationen mit Timeouts und Fehlerbehandlung
"""
import os
import sys
import signal
import threading
import time
import atexit
from typing import List, Callable, Dict, Any, Optional
from datetime import datetime
from contextlib import contextmanager
import logging
# Logging-Setup mit Fallback
try:
from utils.logging_config import get_logger
shutdown_logger = get_logger("shutdown_manager")
except ImportError:
logging.basicConfig(level=logging.INFO)
shutdown_logger = logging.getLogger("shutdown_manager")
class ShutdownManager:
"""
Zentraler Manager für ordnungsgemäße Anwendungsbeendigung.
Koordiniert alle Cleanup-Operationen mit Timeouts und Prioritäten.
"""
def __init__(self, timeout: int = 30):
self.timeout = timeout
self.is_shutting_down = False
self.shutdown_started = None
self.cleanup_functions: List[Dict[str, Any]] = []
self.components: Dict[str, Any] = {}
self._lock = threading.Lock()
self._signal_handlers_registered = False
# Shutdown-Ereignis für Thread-Koordination
self.shutdown_event = threading.Event()
# Registriere grundlegende Signal-Handler
self._register_signal_handlers()
shutdown_logger.info("🔧 Shutdown-Manager initialisiert")
def _register_signal_handlers(self):
"""Registriert plattformspezifische Signal-Handler"""
if self._signal_handlers_registered:
return
try:
if os.name == 'nt': # Windows
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
if hasattr(signal, 'SIGBREAK'):
signal.signal(signal.SIGBREAK, self._signal_handler)
else: # Unix/Linux
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGHUP, self._signal_handler)
self._signal_handlers_registered = True
shutdown_logger.debug("✅ Signal-Handler registriert")
except Exception as e:
shutdown_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {e}")
def _signal_handler(self, signum, frame):
"""Zentraler Signal-Handler für ordnungsgemäße Beendigung"""
if not self.is_shutting_down:
shutdown_logger.warning(f"🛑 Signal {signum} empfangen - starte koordiniertes Shutdown")
self.shutdown()
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
"""
Registriert eine Komponente für ordnungsgemäße Beendigung
Args:
name: Name der Komponente
component: Komponenten-Objekt
stop_method: Name der Stopp-Methode (Standard: "stop")
"""
with self._lock:
self.components[name] = {
'component': component,
'stop_method': stop_method,
'priority': 1 # Standard-Priorität
}
shutdown_logger.debug(f"📝 Komponente '{name}' registriert")
def register_cleanup_function(self,
func: Callable,
name: str,
priority: int = 1,
timeout: int = 10,
args: tuple = (),
kwargs: dict = None):
"""
Registriert eine Cleanup-Funktion
Args:
func: Cleanup-Funktion
name: Beschreibender Name
priority: Priorität (1=hoch, 3=niedrig)
timeout: Timeout in Sekunden
args: Funktions-Argumente
kwargs: Funktions-Keyword-Argumente
"""
if kwargs is None:
kwargs = {}
cleanup_item = {
'function': func,
'name': name,
'priority': priority,
'timeout': timeout,
'args': args,
'kwargs': kwargs
}
with self._lock:
self.cleanup_functions.append(cleanup_item)
# Nach Priorität sortieren (1 = höchste Priorität zuerst)
self.cleanup_functions.sort(key=lambda x: x['priority'])
shutdown_logger.debug(f"📝 Cleanup-Funktion '{name}' registriert (Priorität: {priority})")
def register_queue_manager(self, queue_manager_module):
"""Registriert den Queue Manager für ordnungsgemäße Beendigung"""
try:
stop_func = getattr(queue_manager_module, 'stop_queue_manager', None)
if stop_func:
self.register_cleanup_function(
func=stop_func,
name="Queue Manager",
priority=1, # Hohe Priorität
timeout=15
)
shutdown_logger.debug("✅ Queue Manager registriert")
else:
shutdown_logger.warning("⚠️ Queue Manager stop_queue_manager Funktion nicht gefunden")
except Exception as e:
shutdown_logger.error(f"❌ Fehler beim Registrieren des Queue Managers: {e}")
def register_scheduler(self, scheduler, scheduler_enabled: bool = True):
"""Registriert den Job-Scheduler für ordnungsgemäße Beendigung"""
if not scheduler_enabled or not scheduler:
shutdown_logger.debug("Scheduler nicht registriert (deaktiviert oder nicht vorhanden)")
return
def stop_scheduler():
try:
if hasattr(scheduler, 'shutdown'):
shutdown_logger.info("🔄 Beende Scheduler mit shutdown()...")
scheduler.shutdown(wait=True)
elif hasattr(scheduler, 'stop'):
shutdown_logger.info("🔄 Beende Scheduler mit stop()...")
scheduler.stop()
else:
shutdown_logger.warning("⚠️ Scheduler hat keine bekannte Stop-Methode")
shutdown_logger.info("✅ Scheduler erfolgreich gestoppt")
except Exception as e:
shutdown_logger.error(f"❌ Fehler beim Stoppen des Schedulers: {e}")
self.register_cleanup_function(
func=stop_scheduler,
name="Job Scheduler",
priority=1, # Hohe Priorität
timeout=10
)
def register_database_cleanup(self):
"""Registriert Datenbank-Cleanup mit robuster Fehlerbehandlung"""
def safe_database_cleanup():
try:
shutdown_logger.info("💾 Führe sicheres Datenbank-Cleanup durch...")
# Versuche den neuen DatabaseCleanupManager zu verwenden
try:
from utils.database_cleanup import safe_database_cleanup
result = safe_database_cleanup(force_mode_switch=False) # Kein riskantes Mode-Switching
if result.get("success", False):
shutdown_logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(result.get('operations', []))}")
else:
shutdown_logger.warning(f"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(result.get('errors', []))}")
except ImportError:
shutdown_logger.info("Fallback: Verwende einfaches WAL-Checkpoint...")
# Einfacher Fallback nur mit WAL-Checkpoint
try:
from models import create_optimized_engine
from sqlalchemy import text
engine = create_optimized_engine()
with engine.connect() as conn:
result = conn.execute(text("PRAGMA wal_checkpoint(PASSIVE)")).fetchone()
if result and result[1] > 0:
shutdown_logger.info(f"WAL-Checkpoint: {result[1]} Seiten übertragen")
conn.commit()
engine.dispose()
shutdown_logger.info("✅ Einfaches Datenbank-Cleanup abgeschlossen")
except Exception as db_error:
shutdown_logger.error(f"❌ Fehler beim einfachen Datenbank-Cleanup: {db_error}")
except Exception as cleanup_error:
shutdown_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {cleanup_error}")
self.register_cleanup_function(
func=safe_database_cleanup,
name="Datenbank Cleanup",
priority=3, # Niedrige Priorität (am Ende)
timeout=20
)
def register_windows_thread_manager(self):
"""Registriert Windows Thread Manager falls verfügbar"""
try:
if os.name == 'nt':
from utils.windows_fixes import get_windows_thread_manager
thread_manager = get_windows_thread_manager()
if thread_manager:
self.register_cleanup_function(
func=thread_manager.shutdown_all,
name="Windows Thread Manager",
priority=2,
timeout=15
)
shutdown_logger.debug("✅ Windows Thread Manager registriert")
except Exception as e:
shutdown_logger.debug(f"Windows Thread Manager nicht verfügbar: {e}")
@contextmanager
def cleanup_timeout(self, timeout: int, operation_name: str):
"""Context Manager für Timeout-überwachte Operationen"""
start_time = time.time()
success = False
try:
yield
success = True
except Exception as e:
elapsed = time.time() - start_time
shutdown_logger.error(f"❌ Fehler bei {operation_name} nach {elapsed:.1f}s: {e}")
raise
finally:
elapsed = time.time() - start_time
if success:
shutdown_logger.debug(f"{operation_name} abgeschlossen in {elapsed:.1f}s")
elif elapsed >= timeout:
shutdown_logger.warning(f"⏱️ {operation_name} Timeout nach {elapsed:.1f}s")
def shutdown(self, exit_code: int = 0):
"""
Führt koordiniertes Shutdown aller registrierten Komponenten durch
Args:
exit_code: Exit-Code für sys.exit()
"""
if self.is_shutting_down:
shutdown_logger.debug("Shutdown bereits in Bearbeitung")
return
with self._lock:
if self.is_shutting_down:
return
self.is_shutting_down = True
self.shutdown_started = datetime.now()
shutdown_logger.info("🔄 Starte koordiniertes System-Shutdown...")
self.shutdown_event.set()
total_start = time.time()
try:
# 1. Komponenten stoppen (nach Priorität)
self._shutdown_components()
# 2. Cleanup-Funktionen ausführen (nach Priorität)
self._execute_cleanup_functions()
total_elapsed = time.time() - total_start
shutdown_logger.info(f"✅ Koordiniertes Shutdown abgeschlossen in {total_elapsed:.1f}s")
except Exception as e:
shutdown_logger.error(f"❌ Fehler beim koordinierten Shutdown: {e}")
finally:
shutdown_logger.info("🏁 System wird beendet...")
sys.exit(exit_code)
def _shutdown_components(self):
"""Stoppt alle registrierten Komponenten"""
if not self.components:
return
shutdown_logger.info(f"🔄 Stoppe {len(self.components)} registrierte Komponenten...")
for name, config in self.components.items():
try:
component = config['component']
stop_method = config['stop_method']
if hasattr(component, stop_method):
with self.cleanup_timeout(10, f"Komponente {name}"):
method = getattr(component, stop_method)
method()
shutdown_logger.debug(f"✅ Komponente '{name}' gestoppt")
else:
shutdown_logger.warning(f"⚠️ Komponente '{name}' hat keine '{stop_method}' Methode")
except Exception as e:
shutdown_logger.error(f"❌ Fehler beim Stoppen der Komponente '{name}': {e}")
def _execute_cleanup_functions(self):
"""Führt alle registrierten Cleanup-Funktionen aus"""
if not self.cleanup_functions:
return
shutdown_logger.info(f"🧹 Führe {len(self.cleanup_functions)} Cleanup-Funktionen aus...")
for cleanup_item in self.cleanup_functions:
try:
func = cleanup_item['function']
name = cleanup_item['name']
timeout = cleanup_item['timeout']
args = cleanup_item['args']
kwargs = cleanup_item['kwargs']
shutdown_logger.debug(f"🔄 Führe Cleanup aus: {name}")
with self.cleanup_timeout(timeout, f"Cleanup {name}"):
func(*args, **kwargs)
shutdown_logger.debug(f"✅ Cleanup '{name}' abgeschlossen")
except Exception as e:
shutdown_logger.error(f"❌ Fehler bei Cleanup '{cleanup_item['name']}': {e}")
# Weiter mit nächstem Cleanup
continue
def is_shutdown_requested(self) -> bool:
"""Prüft ob Shutdown angefordert wurde"""
return self.shutdown_event.is_set()
def get_shutdown_time(self) -> Optional[datetime]:
"""Gibt den Zeitpunkt des Shutdown-Starts zurück"""
return self.shutdown_started
def force_shutdown(self, exit_code: int = 1):
"""Erzwingt sofortiges Shutdown ohne Cleanup"""
shutdown_logger.warning("🚨 Erzwinge sofortiges Shutdown ohne Cleanup!")
sys.exit(exit_code)
# Globaler Shutdown-Manager
_shutdown_manager: Optional[ShutdownManager] = None
_manager_lock = threading.Lock()
def get_shutdown_manager(timeout: int = 30) -> ShutdownManager:
"""
Singleton-Pattern für globalen Shutdown-Manager
Args:
timeout: Gesamttimeout für Shutdown-Operationen
Returns:
ShutdownManager: Globaler Shutdown-Manager
"""
global _shutdown_manager
with _manager_lock:
if _shutdown_manager is None:
_shutdown_manager = ShutdownManager(timeout=timeout)
# Registriere atexit-Handler als Fallback
atexit.register(_shutdown_manager.shutdown)
return _shutdown_manager
def register_for_shutdown(component_or_function,
name: str,
component_stop_method: str = "stop",
priority: int = 1,
timeout: int = 10):
"""
Vereinfachte Registrierung für Shutdown
Args:
component_or_function: Komponente mit Stop-Methode oder Cleanup-Funktion
name: Beschreibender Name
component_stop_method: Name der Stop-Methode für Komponenten
priority: Priorität (1=hoch, 3=niedrig)
timeout: Timeout in Sekunden
"""
manager = get_shutdown_manager()
if callable(component_or_function):
# Es ist eine Funktion
manager.register_cleanup_function(
func=component_or_function,
name=name,
priority=priority,
timeout=timeout
)
else:
# Es ist eine Komponente
manager.register_component(
name=name,
component=component_or_function,
stop_method=component_stop_method
)
def shutdown_application(exit_code: int = 0):
"""
Startet koordiniertes Shutdown der Anwendung
Args:
exit_code: Exit-Code für sys.exit()
"""
manager = get_shutdown_manager()
manager.shutdown(exit_code)
def is_shutdown_requested() -> bool:
"""Prüft ob Shutdown angefordert wurde"""
if _shutdown_manager:
return _shutdown_manager.is_shutdown_requested()
return False

View File

@ -1,658 +0,0 @@
#!/usr/bin/env python3
"""
Robuste System-Control-Funktionen für wartungsfreien Produktionsbetrieb
Bietet sichere Restart-, Shutdown- und Kiosk-Verwaltungsfunktionen
"""
import os
import sys
import subprocess
import time
import signal
import psutil
import threading
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Any
from pathlib import Path
import logging
import json
from contextlib import contextmanager
from enum import Enum
# Logging-Setup
try:
from utils.logging_config import get_logger
system_logger = get_logger("system_control")
except ImportError:
logging.basicConfig(level=logging.INFO)
system_logger = logging.getLogger("system_control")
class SystemOperation(Enum):
"""Verfügbare System-Operationen"""
RESTART = "restart"
SHUTDOWN = "shutdown"
KIOSK_RESTART = "kiosk_restart"
KIOSK_ENABLE = "kiosk_enable"
KIOSK_DISABLE = "kiosk_disable"
SERVICE_RESTART = "service_restart"
EMERGENCY_STOP = "emergency_stop"
class SystemControlManager:
"""
Zentraler Manager für alle System-Control-Operationen.
Bietet sichere und robuste Funktionen für wartungsfreien Betrieb.
"""
def __init__(self):
self.is_windows = os.name == 'nt'
self.pending_operations: Dict[str, Dict] = {}
self.operation_history: List[Dict] = []
self.lock = threading.Lock()
# Konfiguration
self.config = {
"restart_delay": 60, # Sekunden
"shutdown_delay": 30, # Sekunden
"kiosk_restart_delay": 10, # Sekunden
"max_operation_history": 100,
"safety_checks": True,
"require_confirmation": True
}
# Service-Namen für verschiedene Plattformen
self.services = {
"https": "myp-https.service",
"kiosk": "myp-kiosk.service",
"watchdog": "kiosk-watchdog.service"
}
system_logger.info("🔧 System-Control-Manager initialisiert")
def is_safe_to_operate(self) -> Tuple[bool, str]:
"""
Prüft ob System-Operationen sicher ausgeführt werden können.
Returns:
Tuple[bool, str]: (is_safe, reason)
"""
try:
# Prüfe Systemlast
load_avg = psutil.getloadavg()[0] if hasattr(psutil, 'getloadavg') else 0
if load_avg > 2.0:
return False, f"Hohe Systemlast: {load_avg:.2f}"
# Prüfe verfügbaren Speicher
memory = psutil.virtual_memory()
if memory.percent > 90:
return False, f"Wenig verfügbarer Speicher: {memory.percent:.1f}% belegt"
# Prüfe aktive Drucker-Jobs
try:
from models import get_db_session, Job
db_session = get_db_session()
active_jobs = db_session.query(Job).filter(
Job.status.in_(["printing", "queued", "preparing"])
).count()
db_session.close()
if active_jobs > 0:
return False, f"Aktive Druckjobs: {active_jobs}"
except Exception as e:
system_logger.warning(f"Job-Prüfung fehlgeschlagen: {e}")
# Prüfe kritische Prozesse
critical_processes = ["chromium", "firefox", "python"]
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent']):
try:
if any(crit in proc.info['name'].lower() for crit in critical_processes):
if proc.info['cpu_percent'] > 80:
return False, f"Kritischer Prozess unter hoher Last: {proc.info['name']}"
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
return True, "System ist sicher für Operationen"
except Exception as e:
system_logger.error(f"Fehler bei Sicherheitsprüfung: {e}")
return False, f"Sicherheitsprüfung fehlgeschlagen: {e}"
def schedule_operation(self,
operation: SystemOperation,
delay_seconds: int = None,
user_id: str = None,
reason: str = None,
force: bool = False) -> Dict[str, Any]:
"""
Plant eine System-Operation mit Verzögerung.
Args:
operation: Art der Operation
delay_seconds: Verzögerung in Sekunden (None = Standard)
user_id: ID des anfragenden Benutzers
reason: Grund für die Operation
force: Sicherheitsprüfungen überspringen
Returns:
Dict mit Operation-Details
"""
with self.lock:
# Sicherheitsprüfung (außer bei Force)
if not force and self.config["safety_checks"]:
is_safe, safety_reason = self.is_safe_to_operate()
if not is_safe:
return {
"success": False,
"error": f"Operation abgelehnt: {safety_reason}",
"safety_check": False
}
# Standard-Verzögerung setzen
if delay_seconds is None:
delay_seconds = {
SystemOperation.RESTART: self.config["restart_delay"],
SystemOperation.SHUTDOWN: self.config["shutdown_delay"],
SystemOperation.KIOSK_RESTART: self.config["kiosk_restart_delay"],
SystemOperation.KIOSK_ENABLE: 5,
SystemOperation.KIOSK_DISABLE: 5,
SystemOperation.SERVICE_RESTART: 10,
SystemOperation.EMERGENCY_STOP: 0
}.get(operation, 30)
# Operations-ID generieren
operation_id = f"{operation.value}_{int(time.time())}"
scheduled_time = datetime.now() + timedelta(seconds=delay_seconds)
# Operation speichern
operation_data = {
"id": operation_id,
"operation": operation.value,
"scheduled_time": scheduled_time,
"delay_seconds": delay_seconds,
"user_id": user_id,
"reason": reason or "Keine Begründung angegeben",
"force": force,
"created_at": datetime.now(),
"status": "scheduled"
}
self.pending_operations[operation_id] = operation_data
# Operation in separatem Thread ausführen
thread = threading.Thread(
target=self._execute_delayed_operation,
args=(operation_id,),
daemon=True
)
thread.start()
system_logger.info(f"🕐 Operation geplant: {operation.value} in {delay_seconds}s")
return {
"success": True,
"operation_id": operation_id,
"scheduled_time": scheduled_time.isoformat(),
"delay_seconds": delay_seconds,
"message": f"Operation '{operation.value}' geplant für {scheduled_time.strftime('%H:%M:%S')}"
}
def _execute_delayed_operation(self, operation_id: str):
"""
Führt geplante Operation nach Verzögerung aus.
Args:
operation_id: ID der auszuführenden Operation
"""
try:
operation_data = self.pending_operations.get(operation_id)
if not operation_data:
return
# Warten bis zur geplanten Zeit
scheduled_time = operation_data["scheduled_time"]
wait_time = (scheduled_time - datetime.now()).total_seconds()
if wait_time > 0:
time.sleep(wait_time)
# Status aktualisieren
operation_data["status"] = "executing"
operation_data["executed_at"] = datetime.now()
# Operation ausführen
operation = SystemOperation(operation_data["operation"])
result = self._execute_operation(operation, operation_data)
# Ergebnis speichern
operation_data["result"] = result
operation_data["status"] = "completed" if result.get("success") else "failed"
operation_data["completed_at"] = datetime.now()
# In Historie verschieben
self._move_to_history(operation_id)
except Exception as e:
system_logger.error(f"Fehler bei verzögerter Operation {operation_id}: {e}")
if operation_id in self.pending_operations:
self.pending_operations[operation_id]["status"] = "error"
self.pending_operations[operation_id]["error"] = str(e)
self._move_to_history(operation_id)
def _execute_operation(self, operation: SystemOperation, operation_data: Dict) -> Dict[str, Any]:
"""
Führt die eigentliche System-Operation aus.
Args:
operation: Art der Operation
operation_data: Operation-Daten
Returns:
Dict mit Ergebnis
"""
try:
system_logger.info(f"▶️ Führe Operation aus: {operation.value}")
if operation == SystemOperation.RESTART:
return self._restart_system(operation_data)
elif operation == SystemOperation.SHUTDOWN:
return self._shutdown_system(operation_data)
elif operation == SystemOperation.KIOSK_RESTART:
return self._restart_kiosk(operation_data)
elif operation == SystemOperation.KIOSK_ENABLE:
return self._enable_kiosk(operation_data)
elif operation == SystemOperation.KIOSK_DISABLE:
return self._disable_kiosk(operation_data)
elif operation == SystemOperation.SERVICE_RESTART:
return self._restart_services(operation_data)
elif operation == SystemOperation.EMERGENCY_STOP:
return self._emergency_stop(operation_data)
else:
return {"success": False, "error": f"Unbekannte Operation: {operation.value}"}
except Exception as e:
system_logger.error(f"Fehler bei Operation {operation.value}: {e}")
return {"success": False, "error": str(e)}
def _restart_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet das System neu."""
try:
system_logger.warning("🔄 System-Neustart wird ausgeführt...")
# Cleanup vor Neustart
self._cleanup_before_restart()
# System-Neustart je nach Plattform
if self.is_windows:
subprocess.run(["shutdown", "/r", "/t", "0"], check=True)
else:
subprocess.run(["sudo", "systemctl", "reboot"], check=True)
return {"success": True, "message": "System-Neustart initiiert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Neustart fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _shutdown_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Fährt das System herunter."""
try:
system_logger.warning("🛑 System-Shutdown wird ausgeführt...")
# Cleanup vor Shutdown
self._cleanup_before_restart()
# System-Shutdown je nach Plattform
if self.is_windows:
subprocess.run(["shutdown", "/s", "/t", "0"], check=True)
else:
subprocess.run(["sudo", "systemctl", "poweroff"], check=True)
return {"success": True, "message": "System-Shutdown initiiert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Shutdown fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _restart_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet nur den Kiosk-Modus neu."""
try:
system_logger.info("🖥️ Kiosk-Neustart wird ausgeführt...")
success_count = 0
errors = []
# Kiosk-Service neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["kiosk"]],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ Kiosk-Service neugestartet")
except Exception as e:
errors.append(f"Kiosk-Service: {e}")
# Watchdog-Service neustarten (falls vorhanden)
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["watchdog"]],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ Watchdog-Service neugestartet")
except Exception as e:
errors.append(f"Watchdog-Service: {e}")
# X11-Session neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", "getty@tty1.service"],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ X11-Session neugestartet")
except Exception as e:
errors.append(f"X11-Session: {e}")
if success_count > 0:
return {
"success": True,
"message": f"Kiosk neugestartet ({success_count} Services)",
"errors": errors if errors else None
}
else:
return {
"success": False,
"error": "Alle Kiosk-Neustarts fehlgeschlagen",
"details": errors
}
except Exception as e:
return {"success": False, "error": f"Kiosk-Neustart fehlgeschlagen: {e}"}
def _enable_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Aktiviert den Kiosk-Modus."""
try:
system_logger.info("🖥️ Kiosk-Modus wird aktiviert...")
# Kiosk-Service aktivieren und starten
subprocess.run(["sudo", "systemctl", "enable", self.services["kiosk"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "start", self.services["kiosk"]],
check=True, timeout=30)
# Watchdog aktivieren
try:
subprocess.run(["sudo", "systemctl", "enable", self.services["watchdog"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "start", self.services["watchdog"]],
check=True, timeout=30)
except Exception as e:
system_logger.warning(f"Watchdog-Aktivierung fehlgeschlagen: {e}")
return {"success": True, "message": "Kiosk-Modus aktiviert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Kiosk-Aktivierung fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _disable_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Deaktiviert den Kiosk-Modus."""
try:
system_logger.info("🖥️ Kiosk-Modus wird deaktiviert...")
# Kiosk-Service stoppen und deaktivieren
subprocess.run(["sudo", "systemctl", "stop", self.services["kiosk"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "disable", self.services["kiosk"]],
check=True, timeout=30)
# Watchdog stoppen
try:
subprocess.run(["sudo", "systemctl", "stop", self.services["watchdog"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "disable", self.services["watchdog"]],
check=True, timeout=30)
except Exception as e:
system_logger.warning(f"Watchdog-Deaktivierung fehlgeschlagen: {e}")
return {"success": True, "message": "Kiosk-Modus deaktiviert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Kiosk-Deaktivierung fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _restart_services(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet wichtige Services neu."""
try:
system_logger.info("🔄 Services werden neugestartet...")
success_count = 0
errors = []
# HTTPS-Service neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["https"]],
check=True, timeout=60)
success_count += 1
system_logger.info("✅ HTTPS-Service neugestartet")
except Exception as e:
errors.append(f"HTTPS-Service: {e}")
# NetworkManager neustarten (falls nötig)
try:
subprocess.run(["sudo", "systemctl", "restart", "NetworkManager"],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ NetworkManager neugestartet")
except Exception as e:
errors.append(f"NetworkManager: {e}")
if success_count > 0:
return {
"success": True,
"message": f"Services neugestartet ({success_count})",
"errors": errors if errors else None
}
else:
return {
"success": False,
"error": "Alle Service-Neustarts fehlgeschlagen",
"details": errors
}
except Exception as e:
return {"success": False, "error": f"Service-Neustart fehlgeschlagen: {e}"}
def _emergency_stop(self, operation_data: Dict) -> Dict[str, Any]:
"""Notfall-Stopp aller Services."""
try:
system_logger.warning("🚨 Notfall-Stopp wird ausgeführt...")
# Flask-App stoppen
try:
os.kill(os.getpid(), signal.SIGTERM)
except Exception as e:
system_logger.error(f"Flask-Stopp fehlgeschlagen: {e}")
return {"success": True, "message": "Notfall-Stopp initiiert"}
except Exception as e:
return {"success": False, "error": f"Notfall-Stopp fehlgeschlagen: {e}"}
def _cleanup_before_restart(self):
"""Führt Cleanup-Operationen vor Neustart/Shutdown aus."""
try:
system_logger.info("🧹 Cleanup vor Neustart/Shutdown...")
# Shutdown-Manager verwenden falls verfügbar
try:
from utils.shutdown_manager import get_shutdown_manager
shutdown_manager = get_shutdown_manager()
shutdown_manager.shutdown(exit_code=0)
except ImportError:
system_logger.warning("Shutdown-Manager nicht verfügbar")
# Datenbank-Cleanup
try:
from utils.database_cleanup import safe_database_cleanup
safe_database_cleanup(force_mode_switch=False)
except ImportError:
system_logger.warning("Database-Cleanup nicht verfügbar")
# Cache leeren
self._clear_caches()
except Exception as e:
system_logger.error(f"Cleanup fehlgeschlagen: {e}")
def _clear_caches(self):
"""Leert alle Caches."""
try:
# User-Cache leeren
from app import clear_user_cache, clear_printer_status_cache
clear_user_cache()
clear_printer_status_cache()
# System-Cache leeren
if not self.is_windows:
subprocess.run(["sudo", "sync"], timeout=10)
subprocess.run(["sudo", "echo", "3", ">", "/proc/sys/vm/drop_caches"],
shell=True, timeout=10)
except Exception as e:
system_logger.warning(f"Cache-Clearing fehlgeschlagen: {e}")
def _move_to_history(self, operation_id: str):
"""Verschiebt abgeschlossene Operation in Historie."""
with self.lock:
if operation_id in self.pending_operations:
operation_data = self.pending_operations.pop(operation_id)
self.operation_history.append(operation_data)
# Historie begrenzen
if len(self.operation_history) > self.config["max_operation_history"]:
self.operation_history = self.operation_history[-self.config["max_operation_history"]:]
def cancel_operation(self, operation_id: str) -> Dict[str, Any]:
"""
Bricht geplante Operation ab.
Args:
operation_id: ID der abzubrechenden Operation
Returns:
Dict mit Ergebnis
"""
with self.lock:
if operation_id not in self.pending_operations:
return {"success": False, "error": "Operation nicht gefunden"}
operation_data = self.pending_operations[operation_id]
if operation_data["status"] == "executing":
return {"success": False, "error": "Operation bereits in Ausführung"}
operation_data["status"] = "cancelled"
operation_data["cancelled_at"] = datetime.now()
self._move_to_history(operation_id)
system_logger.info(f"❌ Operation abgebrochen: {operation_id}")
return {"success": True, "message": "Operation erfolgreich abgebrochen"}
def get_pending_operations(self) -> List[Dict]:
"""Gibt alle geplanten Operationen zurück."""
with self.lock:
return list(self.pending_operations.values())
def get_operation_history(self, limit: int = 20) -> List[Dict]:
"""Gibt Operation-Historie zurück."""
with self.lock:
return self.operation_history[-limit:] if limit else self.operation_history
def get_system_status(self) -> Dict[str, Any]:
"""Gibt aktuellen System-Status zurück."""
try:
# Service-Status prüfen
service_status = {}
for name, service in self.services.items():
try:
result = subprocess.run(
["sudo", "systemctl", "is-active", service],
capture_output=True, text=True, timeout=10
)
service_status[name] = result.stdout.strip()
except Exception as e:
service_status[name] = f"error: {e}"
# System-Metriken
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
# Aktive Operations
pending_ops = len(self.pending_operations)
return {
"success": True,
"timestamp": datetime.now().isoformat(),
"services": service_status,
"system_metrics": {
"memory_percent": memory.percent,
"memory_available_gb": memory.available / (1024**3),
"disk_percent": disk.percent,
"disk_free_gb": disk.free / (1024**3),
"load_average": psutil.getloadavg()[0] if hasattr(psutil, 'getloadavg') else 0
},
"operations": {
"pending": pending_ops,
"history_count": len(self.operation_history)
},
"is_safe": self.is_safe_to_operate()[0]
}
except Exception as e:
return {"success": False, "error": str(e)}
# Globaler System-Control-Manager
_system_control_manager: Optional[SystemControlManager] = None
_control_lock = threading.Lock()
def get_system_control_manager() -> SystemControlManager:
"""
Singleton-Pattern für globalen System-Control-Manager.
Returns:
SystemControlManager: Globaler System-Control-Manager
"""
global _system_control_manager
with _control_lock:
if _system_control_manager is None:
_system_control_manager = SystemControlManager()
return _system_control_manager
# Convenience-Funktionen
def schedule_system_restart(delay_seconds: int = 60, user_id: str = None, reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Plant System-Neustart."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.RESTART, delay_seconds, user_id, reason, force)
def schedule_system_shutdown(delay_seconds: int = 30, user_id: str = None, reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Plant System-Shutdown."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.SHUTDOWN, delay_seconds, user_id, reason, force)
def restart_kiosk(delay_seconds: int = 10, user_id: str = None, reason: str = None) -> Dict[str, Any]:
"""Plant Kiosk-Neustart."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.KIOSK_RESTART, delay_seconds, user_id, reason)
def get_system_status() -> Dict[str, Any]:
"""Gibt System-Status zurück."""
manager = get_system_control_manager()
return manager.get_system_status()