init
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Service Bus for Personal Internet Cell
|
||||
Event-driven service communication and orchestration
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Callable, Any, Optional
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
import queue
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EventType(Enum):
|
||||
"""Event types for service communication"""
|
||||
SERVICE_STARTED = "service_started"
|
||||
SERVICE_STOPPED = "service_stopped"
|
||||
SERVICE_RESTARTED = "service_restarted"
|
||||
CONFIG_CHANGED = "config_changed"
|
||||
HEALTH_CHECK = "health_check"
|
||||
ERROR_OCCURRED = "error_occurred"
|
||||
PEER_CONNECTED = "peer_connected"
|
||||
PEER_DISCONNECTED = "peer_disconnected"
|
||||
SECRET_ROTATED = "secret_rotated"
|
||||
CERTIFICATE_EXPIRING = "certificate_expiring"
|
||||
BACKUP_CREATED = "backup_created"
|
||||
RESTORE_COMPLETED = "restore_completed"
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
"""Event data structure"""
|
||||
event_type: EventType
|
||||
source: str
|
||||
data: Dict[str, Any]
|
||||
timestamp: datetime
|
||||
event_id: str
|
||||
|
||||
class ServiceBus:
|
||||
"""Event-driven service communication bus"""
|
||||
|
||||
def __init__(self):
|
||||
self.event_handlers: Dict[EventType, List[Callable]] = defaultdict(list)
|
||||
self.service_registry: Dict[str, Any] = {}
|
||||
self.event_queue = queue.Queue()
|
||||
self.running = False
|
||||
self.event_loop_thread = None
|
||||
self.event_history: List[Event] = []
|
||||
self.max_history = 1000
|
||||
|
||||
# Service dependency mapping
|
||||
self.service_dependencies: Dict[str, List[str]] = {
|
||||
'wireguard': ['network'],
|
||||
'email': ['network', 'vault'],
|
||||
'calendar': ['network', 'vault'],
|
||||
'files': ['network', 'vault'],
|
||||
'routing': ['network', 'wireguard'],
|
||||
'vault': ['network']
|
||||
}
|
||||
|
||||
# Service lifecycle hooks
|
||||
self.lifecycle_hooks: Dict[str, Dict[str, Callable]] = defaultdict(dict)
|
||||
|
||||
def start(self):
|
||||
"""Start the service bus"""
|
||||
if self.running:
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.event_loop_thread = threading.Thread(target=self._event_loop, daemon=True)
|
||||
self.event_loop_thread.start()
|
||||
logger.info("Service bus started")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the service bus"""
|
||||
self.running = False
|
||||
if self.event_loop_thread:
|
||||
self.event_loop_thread.join(timeout=5)
|
||||
logger.info("Service bus stopped")
|
||||
|
||||
def register_service(self, name: str, service: Any):
|
||||
"""Register a service with the bus"""
|
||||
self.service_registry[name] = service
|
||||
logger.info(f"Registered service: {name}")
|
||||
|
||||
# Publish service started event
|
||||
self.publish_event(EventType.SERVICE_STARTED, name, {
|
||||
"service": name,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
def unregister_service(self, name: str):
|
||||
"""Unregister a service from the bus"""
|
||||
if name in self.service_registry:
|
||||
del self.service_registry[name]
|
||||
logger.info(f"Unregistered service: {name}")
|
||||
|
||||
# Publish service stopped event
|
||||
self.publish_event(EventType.SERVICE_STOPPED, name, {
|
||||
"service": name,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
def publish_event(self, event_type: EventType, source: str, data: Dict[str, Any]):
|
||||
"""Publish an event to the bus"""
|
||||
import uuid
|
||||
|
||||
event = Event(
|
||||
event_type=event_type,
|
||||
source=source,
|
||||
data=data,
|
||||
timestamp=datetime.utcnow(),
|
||||
event_id=str(uuid.uuid4())
|
||||
)
|
||||
|
||||
self.event_queue.put(event)
|
||||
logger.debug(f"Published event: {event_type.value} from {source}")
|
||||
|
||||
def subscribe_to_event(self, event_type: EventType, handler: Callable[[Event], None]):
|
||||
"""Subscribe to an event type"""
|
||||
self.event_handlers[event_type].append(handler)
|
||||
logger.info(f"Subscribed to event: {event_type.value}")
|
||||
|
||||
def unsubscribe_from_event(self, event_type: EventType, handler: Callable[[Event], None]):
|
||||
"""Unsubscribe from an event type"""
|
||||
if event_type in self.event_handlers:
|
||||
try:
|
||||
self.event_handlers[event_type].remove(handler)
|
||||
logger.info(f"Unsubscribed from event: {event_type.value}")
|
||||
except ValueError:
|
||||
logger.warning(f"Handler not found for event: {event_type.value}")
|
||||
|
||||
def call_service(self, service_name: str, method: str, **kwargs) -> Any:
|
||||
"""Call a method on a registered service"""
|
||||
if service_name not in self.service_registry:
|
||||
raise ValueError(f"Service {service_name} not registered")
|
||||
|
||||
service = self.service_registry[service_name]
|
||||
if not hasattr(service, method):
|
||||
raise ValueError(f"Method {method} not found on service {service_name}")
|
||||
|
||||
try:
|
||||
result = getattr(service, method)(**kwargs)
|
||||
logger.debug(f"Called {service_name}.{method}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling {service_name}.{method}: {e}")
|
||||
self.publish_event(EventType.ERROR_OCCURRED, service_name, {
|
||||
"error": str(e),
|
||||
"method": method,
|
||||
"service": service_name
|
||||
})
|
||||
raise
|
||||
|
||||
def get_service(self, service_name: str) -> Any:
|
||||
"""Get a registered service"""
|
||||
return self.service_registry.get(service_name)
|
||||
|
||||
def list_services(self) -> List[str]:
|
||||
"""List all registered services"""
|
||||
return list(self.service_registry.keys())
|
||||
|
||||
def add_lifecycle_hook(self, service_name: str, hook_type: str, hook: Callable):
|
||||
"""Add a lifecycle hook for a service"""
|
||||
self.lifecycle_hooks[service_name][hook_type] = hook
|
||||
logger.info(f"Added {hook_type} hook for {service_name}")
|
||||
|
||||
def remove_lifecycle_hook(self, service_name: str, hook_type: str):
|
||||
"""Remove a lifecycle hook for a service"""
|
||||
if service_name in self.lifecycle_hooks and hook_type in self.lifecycle_hooks[service_name]:
|
||||
del self.lifecycle_hooks[service_name][hook_type]
|
||||
logger.info(f"Removed {hook_type} hook for {service_name}")
|
||||
|
||||
def orchestrate_service_start(self, service_name: str) -> bool:
|
||||
"""Orchestrate starting a service with its dependencies"""
|
||||
try:
|
||||
# Check dependencies
|
||||
dependencies = self.service_dependencies.get(service_name, [])
|
||||
for dep in dependencies:
|
||||
if dep not in self.service_registry:
|
||||
logger.warning(f"Service {service_name} depends on {dep} which is not registered")
|
||||
return False
|
||||
|
||||
# Run pre-start hooks
|
||||
if service_name in self.lifecycle_hooks and 'pre_start' in self.lifecycle_hooks[service_name]:
|
||||
self.lifecycle_hooks[service_name]['pre_start']()
|
||||
|
||||
# Start the service
|
||||
if hasattr(self.service_registry[service_name], 'start'):
|
||||
self.service_registry[service_name].start()
|
||||
|
||||
# Run post-start hooks
|
||||
if service_name in self.lifecycle_hooks and 'post_start' in self.lifecycle_hooks[service_name]:
|
||||
self.lifecycle_hooks[service_name]['post_start']()
|
||||
|
||||
logger.info(f"Orchestrated start of service: {service_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error orchestrating start of {service_name}: {e}")
|
||||
return False
|
||||
|
||||
def orchestrate_service_stop(self, service_name: str) -> bool:
|
||||
"""Orchestrate stopping a service"""
|
||||
try:
|
||||
# Run pre-stop hooks
|
||||
if service_name in self.lifecycle_hooks and 'pre_stop' in self.lifecycle_hooks[service_name]:
|
||||
self.lifecycle_hooks[service_name]['pre_stop']()
|
||||
|
||||
# Stop the service
|
||||
if hasattr(self.service_registry[service_name], 'stop'):
|
||||
self.service_registry[service_name].stop()
|
||||
|
||||
# Run post-stop hooks
|
||||
if service_name in self.lifecycle_hooks and 'post_stop' in self.lifecycle_hooks[service_name]:
|
||||
self.lifecycle_hooks[service_name]['post_stop']()
|
||||
|
||||
logger.info(f"Orchestrated stop of service: {service_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error orchestrating stop of {service_name}: {e}")
|
||||
return False
|
||||
|
||||
def orchestrate_service_restart(self, service_name: str) -> bool:
|
||||
"""Orchestrate restarting a service"""
|
||||
try:
|
||||
if self.orchestrate_service_stop(service_name):
|
||||
return self.orchestrate_service_start(service_name)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error orchestrating restart of {service_name}: {e}")
|
||||
return False
|
||||
|
||||
def get_event_history(self, event_type: Optional[EventType] = None,
|
||||
source: Optional[str] = None, limit: int = 100) -> List[Event]:
|
||||
"""Get event history with optional filtering"""
|
||||
events = self.event_history
|
||||
|
||||
if event_type:
|
||||
events = [e for e in events if e.event_type == event_type]
|
||||
|
||||
if source:
|
||||
events = [e for e in events if e.source == source]
|
||||
|
||||
return events[-limit:]
|
||||
|
||||
def clear_event_history(self):
|
||||
"""Clear event history"""
|
||||
self.event_history.clear()
|
||||
logger.info("Event history cleared")
|
||||
|
||||
def _event_loop(self):
|
||||
"""Main event processing loop"""
|
||||
while self.running:
|
||||
try:
|
||||
# Get event from queue with timeout
|
||||
event = self.event_queue.get(timeout=1)
|
||||
|
||||
# Add to history
|
||||
self.event_history.append(event)
|
||||
if len(self.event_history) > self.max_history:
|
||||
self.event_history.pop(0)
|
||||
|
||||
# Process event handlers
|
||||
handlers = self.event_handlers.get(event.event_type, [])
|
||||
for handler in handlers:
|
||||
try:
|
||||
handler(event)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in event handler for {event.event_type.value}: {e}")
|
||||
|
||||
# Mark task as done
|
||||
self.event_queue.task_done()
|
||||
|
||||
except queue.Empty:
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"Error in event loop: {e}")
|
||||
|
||||
def get_service_dependencies(self, service_name: str) -> List[str]:
|
||||
"""Get dependencies for a service"""
|
||||
return self.service_dependencies.get(service_name, [])
|
||||
|
||||
def add_service_dependency(self, service_name: str, dependency: str):
|
||||
"""Add a dependency for a service"""
|
||||
if service_name not in self.service_dependencies:
|
||||
self.service_dependencies[service_name] = []
|
||||
self.service_dependencies[service_name].append(dependency)
|
||||
logger.info(f"Added dependency {dependency} for service {service_name}")
|
||||
|
||||
def remove_service_dependency(self, service_name: str, dependency: str):
|
||||
"""Remove a dependency for a service"""
|
||||
if service_name in self.service_dependencies:
|
||||
try:
|
||||
self.service_dependencies[service_name].remove(dependency)
|
||||
logger.info(f"Removed dependency {dependency} for service {service_name}")
|
||||
except ValueError:
|
||||
logger.warning(f"Dependency {dependency} not found for service {service_name}")
|
||||
|
||||
def get_service_status_summary(self) -> Dict[str, Any]:
|
||||
"""Get summary of all service statuses"""
|
||||
summary = {
|
||||
"total_services": len(self.service_registry),
|
||||
"services": {},
|
||||
"event_count": len(self.event_history),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
for service_name, service in self.service_registry.items():
|
||||
try:
|
||||
if hasattr(service, 'get_status'):
|
||||
status = service.get_status()
|
||||
else:
|
||||
status = {"status": "unknown"}
|
||||
|
||||
summary["services"][service_name] = {
|
||||
"status": status,
|
||||
"dependencies": self.service_dependencies.get(service_name, [])
|
||||
}
|
||||
except Exception as e:
|
||||
summary["services"][service_name] = {
|
||||
"status": {"error": str(e)},
|
||||
"dependencies": self.service_dependencies.get(service_name, [])
|
||||
}
|
||||
|
||||
return summary
|
||||
Reference in New Issue
Block a user