""" global_state_sync_with_oracle.py — Sincronizzazione Globale con Nodo E (Oracle) Integra il Nodo E (Oracle Cloud) nella logica di sincronizzazione del cluster. Architettura: B (PRIMARY): Stato globale, transazioni critiche C (Memory/RAG): Embeddings, conversazioni D (Audit): Event log, compliance E (Compute): Task distribuiti, carichi pesanti A (Cache): Read-only, analytics Flussi di Sincronizzazione: - B ↔ C: Memoria bidirezionale - B → D: Audit unidirezionale - B → E: Task distribuiti - E → B: Risultati task - A: Nessuna (read-only) """ import os import asyncio import logging from typing import Dict, List, Any, Optional from datetime import datetime, timedelta from enum import Enum import json _logger = logging.getLogger("global_state_sync_oracle") # ── Configurazione Nodi ──────────────────────────────────────────────────── ORACLE_NODE_E_ENABLED = os.getenv("ORACLE_NODE_E_ENABLED", "true").lower() == "true" ORACLE_NODE_E_IP = os.getenv("ORACLE_NODE_E_IP", "80.225.89.217") ORACLE_NODE_E_PORT = int(os.getenv("ORACLE_NODE_E_PORT", "8080")) SYNC_INTERVAL_SECONDS = int(os.getenv("SYNC_INTERVAL_SECONDS", "30")) SYNC_BATCH_SIZE = int(os.getenv("SYNC_BATCH_SIZE", "100")) SYNC_TIMEOUT_SECONDS = int(os.getenv("SYNC_TIMEOUT_SECONDS", "60")) # ── Enumerazione Tipo Sincronizzazione ───────────────────────────────────── class SyncType(str, Enum): MEMORY = "memory" # B ↔ C AUDIT = "audit" # B → D TASK = "task" # B → E RESULT = "result" # E → B CACHE = "cache" # A (read-only) # ── Classe Sincronizzatore ──────────────────────────────────────────────── class GlobalStateSyncWithOracle: """Gestore sincronizzazione globale con Nodo E.""" def __init__(self): self.sync_history = [] self.pending_tasks = [] self.last_sync_time = {} self.sync_stats = { "total_syncs": 0, "successful_syncs": 0, "failed_syncs": 0, "total_items_synced": 0, } async def sync_memory(self, session_id: str) -> Dict[str, Any]: """Sincronizza memoria tra B e C.""" try: _logger.info(f"Sincronizzazione memoria: {session_id}") # Recupera memoria da B memory_b = await self._fetch_from_node_b(f"agent_memory:{session_id}") # Sincronizza su C await self._sync_to_node_c(f"agent_memory:{session_id}", memory_b) # Sincronizza su E (se abilitato e ha task correlati) if ORACLE_NODE_E_ENABLED: await self._sync_to_node_e(f"memory:{session_id}", memory_b) self.sync_stats["successful_syncs"] += 1 self.sync_stats["total_items_synced"] += 1 return { "ok": True, "session_id": session_id, "synced_to": ["B", "C", "E"] if ORACLE_NODE_E_ENABLED else ["B", "C"], } except Exception as exc: _logger.error(f"Errore sincronizzazione memoria: {exc}") self.sync_stats["failed_syncs"] += 1 return {"ok": False, "error": str(exc)} async def sync_audit(self, event: Dict[str, Any]) -> Dict[str, Any]: """Sincronizza evento audit da B a D.""" try: _logger.info(f"Sincronizzazione audit: {event.get('event_type')}") # Sincronizza su D (append-only) await self._sync_to_node_d("audit_event", event) # Log anche su E se è un evento critico if ORACLE_NODE_E_ENABLED and event.get("severity") in ["critical", "error"]: await self._sync_to_node_e("audit_log", event) self.sync_stats["successful_syncs"] += 1 self.sync_stats["total_items_synced"] += 1 return { "ok": True, "event_id": event.get("id"), "synced_to": ["D", "E"] if ORACLE_NODE_E_ENABLED else ["D"], } except Exception as exc: _logger.error(f"Errore sincronizzazione audit: {exc}") self.sync_stats["failed_syncs"] += 1 return {"ok": False, "error": str(exc)} async def distribute_task_to_oracle(self, task: Dict[str, Any]) -> Dict[str, Any]: """Distribuisce un task computazionale al Nodo E (Oracle).""" if not ORACLE_NODE_E_ENABLED: return {"ok": False, "error": "Oracle Node E disabilitato"} try: _logger.info(f"Distribuzione task a Nodo E: {task.get('id')}") # Salva task in B (PRIMARY) task_id = await self._save_task_to_node_b(task) # Invia task a E result = await self._send_task_to_node_e(task_id, task) if result.get("ok"): self.pending_tasks.append({ "task_id": task_id, "node": "E", "created_at": datetime.now().isoformat(), "status": "pending", }) self.sync_stats["successful_syncs"] += 1 else: self.sync_stats["failed_syncs"] += 1 return result except Exception as exc: _logger.error(f"Errore distribuzione task: {exc}") self.sync_stats["failed_syncs"] += 1 return {"ok": False, "error": str(exc)} async def collect_task_results(self) -> Dict[str, Any]: """Raccoglie i risultati dei task dal Nodo E.""" if not ORACLE_NODE_E_ENABLED: return {"ok": False, "error": "Oracle Node E disabilitato"} try: _logger.info("Raccolta risultati task da Nodo E...") results = [] for task_info in self.pending_tasks[:]: if task_info["node"] == "E": result = await self._fetch_task_result_from_node_e(task_info["task_id"]) if result.get("ok") and result.get("status") == "completed": # Sincronizza risultato su B await self._sync_result_to_node_b(task_info["task_id"], result) results.append(result) self.pending_tasks.remove(task_info) self.sync_stats["successful_syncs"] += 1 return { "ok": True, "results_collected": len(results), "pending_tasks": len(self.pending_tasks), "results": results, } except Exception as exc: _logger.error(f"Errore raccolta risultati: {exc}") return {"ok": False, "error": str(exc)} async def sync_all(self) -> Dict[str, Any]: """Sincronizzazione completa tra tutti i nodi.""" try: _logger.info("Sincronizzazione completa iniziata...") start_time = datetime.now() # 1. Sincronizza memoria (B ↔ C) memory_sync = await self._sync_all_memory() # 2. Sincronizza audit (B → D) audit_sync = await self._sync_all_audit() # 3. Raccoglie risultati task da E task_results = None if ORACLE_NODE_E_ENABLED: task_results = await self.collect_task_results() elapsed = (datetime.now() - start_time).total_seconds() self.sync_stats["total_syncs"] += 1 self.last_sync_time["all"] = datetime.now().isoformat() return { "ok": True, "memory_synced": memory_sync.get("count", 0), "audit_synced": audit_sync.get("count", 0), "task_results_collected": task_results.get("results_collected", 0) if task_results else 0, "elapsed_seconds": elapsed, "stats": self.sync_stats, } except Exception as exc: _logger.error(f"Errore sincronizzazione completa: {exc}") self.sync_stats["failed_syncs"] += 1 return {"ok": False, "error": str(exc)} # ── Metodi Privati ──────────────────────────────────────────────────── async def _fetch_from_node_b(self, key: str) -> Any: """Recupera dato da Nodo B (PRIMARY).""" # Implementazione: Query Supabase B _logger.debug(f"Fetch da B: {key}") return {} # Placeholder async def _sync_to_node_c(self, key: str, data: Any) -> bool: """Sincronizza dato a Nodo C (Memory/RAG).""" # Implementazione: Insert/Update Supabase C _logger.debug(f"Sync a C: {key}") return True # Placeholder async def _sync_to_node_d(self, key: str, data: Any) -> bool: """Sincronizza evento audit a Nodo D (Audit).""" # Implementazione: Append-only insert Supabase D _logger.debug(f"Sync a D: {key}") return True # Placeholder async def _sync_to_node_e(self, key: str, data: Any) -> bool: """Sincronizza dato a Nodo E (Oracle Compute).""" if not ORACLE_NODE_E_ENABLED: return False try: import httpx async with httpx.AsyncClient() as client: response = await client.post( f"http://{ORACLE_NODE_E_IP}:{ORACLE_NODE_E_PORT}/api/sync/receive", json={"key": key, "data": data}, timeout=SYNC_TIMEOUT_SECONDS, ) return response.status_code == 200 except Exception as exc: _logger.error(f"Errore sync a E: {exc}") return False async def _save_task_to_node_b(self, task: Dict[str, Any]) -> str: """Salva task in B.""" # Implementazione: Insert Supabase B _logger.debug(f"Save task a B: {task.get('id')}") return task.get("id", "task_unknown") # Placeholder async def _send_task_to_node_e(self, task_id: str, task: Dict[str, Any]) -> Dict[str, Any]: """Invia task a Nodo E.""" if not ORACLE_NODE_E_ENABLED: return {"ok": False, "error": "Oracle Node E disabilitato"} try: import httpx async with httpx.AsyncClient() as client: response = await client.post( f"http://{ORACLE_NODE_E_IP}:{ORACLE_NODE_E_PORT}/api/tasks/submit", json={"task_id": task_id, "task": task}, timeout=SYNC_TIMEOUT_SECONDS, ) return response.json() if response.status_code == 200 else {"ok": False} except Exception as exc: _logger.error(f"Errore invio task a E: {exc}") return {"ok": False, "error": str(exc)} async def _fetch_task_result_from_node_e(self, task_id: str) -> Dict[str, Any]: """Recupera risultato task da Nodo E.""" if not ORACLE_NODE_E_ENABLED: return {"ok": False} try: import httpx async with httpx.AsyncClient() as client: response = await client.get( f"http://{ORACLE_NODE_E_IP}:{ORACLE_NODE_E_PORT}/api/tasks/{task_id}/result", timeout=SYNC_TIMEOUT_SECONDS, ) return response.json() if response.status_code == 200 else {"ok": False} except Exception as exc: _logger.error(f"Errore fetch risultato da E: {exc}") return {"ok": False} async def _sync_result_to_node_b(self, task_id: str, result: Dict[str, Any]) -> bool: """Sincronizza risultato task a B.""" # Implementazione: Update Supabase B _logger.debug(f"Sync risultato a B: {task_id}") return True # Placeholder async def _sync_all_memory(self) -> Dict[str, Any]: """Sincronizza tutta la memoria.""" _logger.debug("Sincronizzazione memoria completa...") return {"count": 0} # Placeholder async def _sync_all_audit(self) -> Dict[str, Any]: """Sincronizza tutti gli audit.""" _logger.debug("Sincronizzazione audit completa...") return {"count": 0} # Placeholder # ── Istanza Globale ─────────────────────────────────────────────────────── _sync_manager = GlobalStateSyncWithOracle() # ── Scheduler Sincronizzazione ──────────────────────────────────────────── async def start_sync_scheduler(): """Avvia lo scheduler di sincronizzazione periodica.""" _logger.info(f"Avvio scheduler sincronizzazione (intervallo: {SYNC_INTERVAL_SECONDS}s)...") while True: try: await asyncio.sleep(SYNC_INTERVAL_SECONDS) result = await _sync_manager.sync_all() if result.get("ok"): _logger.info(f"Sincronizzazione completata: {result}") else: _logger.warning(f"Sincronizzazione parziale: {result}") except Exception as exc: _logger.error(f"Errore scheduler: {exc}") # ── Funzioni Pubbliche ──────────────────────────────────────────────────── async def sync_memory(session_id: str) -> Dict[str, Any]: """Sincronizza memoria.""" return await _sync_manager.sync_memory(session_id) async def sync_audit(event: Dict[str, Any]) -> Dict[str, Any]: """Sincronizza audit.""" return await _sync_manager.sync_audit(event) async def distribute_task(task: Dict[str, Any]) -> Dict[str, Any]: """Distribuisce task a Nodo E.""" return await _sync_manager.distribute_task_to_oracle(task) async def get_sync_stats() -> Dict[str, Any]: """Ritorna statistiche sincronizzazione.""" return _sync_manager.sync_stats