- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2 - Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2 - Made more general-purpose (flexible targets, expanded tools) - Added nemotron-mini-agent.csv for fast local inference via Ollama - Added nemotron-agent.csv for advanced reasoning via Ollama - Added wiki page: Projects for NVIDIA NIMs and Nemotron
371 lines
14 KiB
Python
371 lines
14 KiB
Python
"""
|
|
Message Bus Client for GT 2.0 Tenant Backend
|
|
|
|
Handles communication with Admin Cluster via RabbitMQ message queues.
|
|
Processes commands from admin and sends responses back.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import asyncio
|
|
import uuid
|
|
import hmac
|
|
import hashlib
|
|
from datetime import datetime
|
|
from typing import Dict, Any, Optional, Callable, List
|
|
from pydantic import BaseModel, Field
|
|
|
|
import aio_pika
|
|
from aio_pika import connect, Message, DeliveryMode, ExchangeType
|
|
from aio_pika.abc import AbstractIncomingMessage
|
|
|
|
from app.core.config import get_settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class AdminCommand(BaseModel):
|
|
"""Command received from Admin Cluster"""
|
|
command_id: str
|
|
command_type: str # TENANT_PROVISION, TENANT_SUSPEND, etc.
|
|
target_cluster: str
|
|
target_namespace: str
|
|
payload: Dict[str, Any]
|
|
timestamp: str
|
|
signature: str
|
|
|
|
class TenantResponse(BaseModel):
|
|
"""Response sent to Admin Cluster"""
|
|
command_id: str
|
|
response_type: str # SUCCESS, ERROR, PROCESSING
|
|
target_cluster: str = "admin"
|
|
source_cluster: str = "tenant"
|
|
namespace: str
|
|
payload: Dict[str, Any] = Field(default_factory=dict)
|
|
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
|
|
signature: Optional[str] = None
|
|
|
|
class MessageBusClient:
|
|
"""
|
|
Client for RabbitMQ message bus communication with Admin Cluster.
|
|
|
|
GT 2.0 Security Principles:
|
|
- HMAC signature verification for all messages
|
|
- Tenant-scoped command processing
|
|
- No cross-tenant message leakage
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.settings = get_settings()
|
|
self.connection = None
|
|
self.channel = None
|
|
self.admin_to_tenant_queue = None
|
|
self.tenant_to_admin_queue = None
|
|
|
|
# Message handlers
|
|
self.command_handlers: Dict[str, Callable[[AdminCommand], Any]] = {}
|
|
|
|
# RabbitMQ configuration from admin specification
|
|
self.rabbitmq_url = getattr(
|
|
self.settings,
|
|
'RABBITMQ_URL',
|
|
'amqp://gt2_admin:dev_password_change_in_prod@rabbitmq:5672/'
|
|
)
|
|
|
|
# Security
|
|
self.secret_key = getattr(self.settings, 'SECRET_KEY', 'production-secret-key')
|
|
|
|
logger.info("Message bus client initialized")
|
|
|
|
async def connect(self) -> bool:
|
|
"""Connect to RabbitMQ message bus"""
|
|
try:
|
|
self.connection = await connect(self.rabbitmq_url)
|
|
self.channel = await self.connection.channel()
|
|
|
|
# Declare exchanges and queues matching admin specification
|
|
await self.channel.declare_exchange(
|
|
'gt2_commands', ExchangeType.DIRECT, durable=True
|
|
)
|
|
|
|
# Admin → Tenant command queue
|
|
self.admin_to_tenant_queue = await self.channel.declare_queue(
|
|
'admin_to_tenant', durable=True
|
|
)
|
|
|
|
# Tenant → Admin response queue
|
|
self.tenant_to_admin_queue = await self.channel.declare_queue(
|
|
'tenant_to_admin', durable=True
|
|
)
|
|
|
|
# Start consuming commands
|
|
await self.admin_to_tenant_queue.consume(self._handle_admin_command)
|
|
|
|
logger.info("Connected to RabbitMQ message bus")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect to message bus: {e}")
|
|
return False
|
|
|
|
def _verify_signature(self, message_data: Dict[str, Any], signature: str) -> bool:
|
|
"""Verify HMAC signature of incoming message"""
|
|
try:
|
|
# Create message content for signature (excluding signature field)
|
|
content = {k: v for k, v in message_data.items() if k != 'signature'}
|
|
content_str = json.dumps(content, sort_keys=True)
|
|
|
|
# Calculate expected signature
|
|
expected_signature = hmac.new(
|
|
self.secret_key.encode(),
|
|
content_str.encode(),
|
|
hashlib.sha256
|
|
).hexdigest()
|
|
|
|
return hmac.compare_digest(expected_signature, signature)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Signature verification failed: {e}")
|
|
return False
|
|
|
|
def _sign_message(self, message_data: Dict[str, Any]) -> str:
|
|
"""Create HMAC signature for outgoing message"""
|
|
try:
|
|
content_str = json.dumps(message_data, sort_keys=True)
|
|
return hmac.new(
|
|
self.secret_key.encode(),
|
|
content_str.encode(),
|
|
hashlib.sha256
|
|
).hexdigest()
|
|
except Exception as e:
|
|
logger.error(f"Message signing failed: {e}")
|
|
return ""
|
|
|
|
async def _handle_admin_command(self, message: AbstractIncomingMessage) -> None:
|
|
"""Handle incoming command from Admin Cluster"""
|
|
try:
|
|
# Parse message
|
|
message_data = json.loads(message.body.decode())
|
|
command = AdminCommand(**message_data)
|
|
|
|
logger.info(f"Received admin command: {command.command_type} ({command.command_id})")
|
|
|
|
# Verify signature
|
|
if not self._verify_signature(message_data, command.signature):
|
|
logger.error(f"Invalid signature for command {command.command_id}")
|
|
await self._send_response(command.command_id, "ERROR", {
|
|
"error": "Invalid signature",
|
|
"namespace": command.target_namespace
|
|
})
|
|
return
|
|
|
|
# Check if we have a handler for this command type
|
|
if command.command_type not in self.command_handlers:
|
|
logger.warning(f"No handler for command type: {command.command_type}")
|
|
await self._send_response(command.command_id, "ERROR", {
|
|
"error": f"Unknown command type: {command.command_type}",
|
|
"namespace": command.target_namespace
|
|
})
|
|
return
|
|
|
|
# Execute command handler
|
|
handler = self.command_handlers[command.command_type]
|
|
result = await handler(command)
|
|
|
|
# Send success response
|
|
await self._send_response(command.command_id, "SUCCESS", {
|
|
"result": result,
|
|
"namespace": command.target_namespace
|
|
})
|
|
|
|
# Acknowledge message
|
|
message.ack()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error handling admin command: {e}")
|
|
try:
|
|
if 'command' in locals():
|
|
await self._send_response(command.command_id, "ERROR", {
|
|
"error": str(e),
|
|
"namespace": getattr(command, 'target_namespace', 'unknown')
|
|
})
|
|
message.nack(requeue=False)
|
|
except:
|
|
pass
|
|
|
|
async def _send_response(self, command_id: str, response_type: str, payload: Dict[str, Any]) -> None:
|
|
"""Send response back to Admin Cluster"""
|
|
try:
|
|
response_data = {
|
|
"command_id": command_id,
|
|
"response_type": response_type,
|
|
"target_cluster": "admin",
|
|
"source_cluster": "tenant",
|
|
"namespace": payload.get("namespace", "unknown"),
|
|
"payload": payload,
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# Sign the response
|
|
response_data["signature"] = self._sign_message(response_data)
|
|
|
|
# Send message
|
|
message = Message(
|
|
json.dumps(response_data).encode(),
|
|
delivery_mode=DeliveryMode.PERSISTENT
|
|
)
|
|
|
|
await self.channel.default_exchange.publish(
|
|
message, routing_key=self.tenant_to_admin_queue.name
|
|
)
|
|
|
|
logger.info(f"Sent response to admin: {response_type} for {command_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to send response to admin: {e}")
|
|
|
|
def register_handler(self, command_type: str, handler: Callable[[AdminCommand], Any]) -> None:
|
|
"""Register handler for specific command type"""
|
|
self.command_handlers[command_type] = handler
|
|
logger.info(f"Registered handler for command type: {command_type}")
|
|
|
|
async def send_notification(self, notification_type: str, payload: Dict[str, Any]) -> None:
|
|
"""Send notification to Admin Cluster (not in response to a command)"""
|
|
try:
|
|
notification_data = {
|
|
"command_id": str(uuid.uuid4()),
|
|
"response_type": f"NOTIFICATION_{notification_type}",
|
|
"target_cluster": "admin",
|
|
"source_cluster": "tenant",
|
|
"namespace": getattr(self.settings, 'TENANT_ID', 'unknown'),
|
|
"payload": payload,
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# Sign the notification
|
|
notification_data["signature"] = self._sign_message(notification_data)
|
|
|
|
# Send message
|
|
message = Message(
|
|
json.dumps(notification_data).encode(),
|
|
delivery_mode=DeliveryMode.PERSISTENT
|
|
)
|
|
|
|
await self.channel.default_exchange.publish(
|
|
message, routing_key=self.tenant_to_admin_queue.name
|
|
)
|
|
|
|
logger.info(f"Sent notification to admin: {notification_type}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to send notification to admin: {e}")
|
|
|
|
async def disconnect(self) -> None:
|
|
"""Disconnect from message bus"""
|
|
try:
|
|
if self.connection:
|
|
await self.connection.close()
|
|
logger.info("Disconnected from message bus")
|
|
except Exception as e:
|
|
logger.error(f"Error disconnecting from message bus: {e}")
|
|
|
|
# Global instance
|
|
message_bus_client = MessageBusClient()
|
|
|
|
# Command handlers for different admin commands
|
|
async def handle_tenant_provision(command: AdminCommand) -> Dict[str, Any]:
|
|
"""Handle tenant provisioning command"""
|
|
logger.info(f"Processing tenant provision for: {command.target_namespace}")
|
|
|
|
# TODO: Implement tenant provisioning logic
|
|
# - Create tenant directory structure
|
|
# - Initialize SQLite database
|
|
# - Set up access controls
|
|
# - Configure resource quotas
|
|
|
|
return {
|
|
"status": "provisioned",
|
|
"namespace": command.target_namespace,
|
|
"resources_allocated": command.payload.get("resources", {})
|
|
}
|
|
|
|
async def handle_tenant_suspend(command: AdminCommand) -> Dict[str, Any]:
|
|
"""Handle tenant suspension command"""
|
|
logger.info(f"Processing tenant suspension for: {command.target_namespace}")
|
|
|
|
# TODO: Implement tenant suspension logic
|
|
# - Disable API access
|
|
# - Pause running processes
|
|
# - Preserve data integrity
|
|
|
|
return {
|
|
"status": "suspended",
|
|
"namespace": command.target_namespace
|
|
}
|
|
|
|
async def handle_tenant_activate(command: AdminCommand) -> Dict[str, Any]:
|
|
"""Handle tenant activation command"""
|
|
logger.info(f"Processing tenant activation for: {command.target_namespace}")
|
|
|
|
# TODO: Implement tenant activation logic
|
|
# - Restore API access
|
|
# - Resume processes
|
|
# - Validate system state
|
|
|
|
return {
|
|
"status": "activated",
|
|
"namespace": command.target_namespace
|
|
}
|
|
|
|
async def handle_resource_allocate(command: AdminCommand) -> Dict[str, Any]:
|
|
"""Handle resource allocation command"""
|
|
logger.info(f"Processing resource allocation for: {command.target_namespace}")
|
|
|
|
# TODO: Implement resource allocation logic
|
|
# - Update resource quotas
|
|
# - Configure rate limits
|
|
# - Enable new capabilities
|
|
|
|
return {
|
|
"status": "allocated",
|
|
"namespace": command.target_namespace,
|
|
"resources": command.payload.get("resources", {})
|
|
}
|
|
|
|
async def handle_resource_revoke(command: AdminCommand) -> Dict[str, Any]:
|
|
"""Handle resource revocation command"""
|
|
logger.info(f"Processing resource revocation for: {command.target_namespace}")
|
|
|
|
# TODO: Implement resource revocation logic
|
|
# - Remove resource access
|
|
# - Update quotas
|
|
# - Gracefully handle running operations
|
|
|
|
return {
|
|
"status": "revoked",
|
|
"namespace": command.target_namespace
|
|
}
|
|
|
|
# Register all command handlers
|
|
async def initialize_message_bus() -> bool:
|
|
"""Initialize message bus with all command handlers"""
|
|
try:
|
|
# Register command handlers
|
|
message_bus_client.register_handler("TENANT_PROVISION", handle_tenant_provision)
|
|
message_bus_client.register_handler("TENANT_SUSPEND", handle_tenant_suspend)
|
|
message_bus_client.register_handler("TENANT_ACTIVATE", handle_tenant_activate)
|
|
message_bus_client.register_handler("RESOURCE_ALLOCATE", handle_resource_allocate)
|
|
message_bus_client.register_handler("RESOURCE_REVOKE", handle_resource_revoke)
|
|
|
|
# Connect to message bus
|
|
connected = await message_bus_client.connect()
|
|
|
|
if connected:
|
|
logger.info("Message bus client initialized successfully")
|
|
return True
|
|
else:
|
|
logger.error("Failed to initialize message bus client")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error initializing message bus: {e}")
|
|
return False |