Some checks failed
CLI Tests / test-cli (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
- Add island configuration fields (island_id, island_name, is_hub, island_chain_id, hub_discovery_url, bridge_islands) - Add NAT traversal configuration (STUN/TURN servers and credentials) - Add DEFAULT_ISLAND_ID using UUID for new installations - Extend PeerNode with public_address, public_port, island_id, island_chain_id, and is_hub fields - Update DiscoveryMessage to include island metadata and public endpoint
309 lines
11 KiB
Python
309 lines
11 KiB
Python
"""
|
|
Island Manager
|
|
Manages island membership, multi-island support, and island operations for federated mesh
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import uuid
|
|
from typing import Dict, List, Optional, Set
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
import time
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class IslandStatus(Enum):
|
|
"""Island membership status"""
|
|
ACTIVE = "active"
|
|
INACTIVE = "inactive"
|
|
BRIDGING = "bridging"
|
|
|
|
|
|
@dataclass
|
|
class IslandMembership:
|
|
"""Represents a node's membership in an island"""
|
|
island_id: str
|
|
island_name: str
|
|
chain_id: str
|
|
status: IslandStatus
|
|
joined_at: float
|
|
is_hub: bool = False
|
|
peer_count: int = 0
|
|
|
|
|
|
@dataclass
|
|
class BridgeRequest:
|
|
"""Represents a bridge request to another island"""
|
|
request_id: str
|
|
source_island_id: str
|
|
target_island_id: str
|
|
source_node_id: str
|
|
timestamp: float
|
|
status: str = "pending" # pending, approved, rejected
|
|
|
|
|
|
class IslandManager:
|
|
"""Manages island membership and operations for federated mesh"""
|
|
|
|
def __init__(self, local_node_id: str, default_island_id: str, default_chain_id: str):
|
|
self.local_node_id = local_node_id
|
|
self.default_island_id = default_island_id
|
|
self.default_chain_id = default_chain_id
|
|
|
|
# Island memberships
|
|
self.islands: Dict[str, IslandMembership] = {}
|
|
|
|
# Bridge requests
|
|
self.bridge_requests: Dict[str, BridgeRequest] = {}
|
|
|
|
# Active bridges
|
|
self.active_bridges: Set[str] = set() # island_ids we're bridged to
|
|
|
|
# Island peers (island_id -> set of node_ids)
|
|
self.island_peers: Dict[str, Set[str]] = {}
|
|
|
|
self.running = False
|
|
|
|
# Initialize with default island
|
|
self._initialize_default_island()
|
|
|
|
def _initialize_default_island(self):
|
|
"""Initialize with default island membership"""
|
|
self.islands[self.default_island_id] = IslandMembership(
|
|
island_id=self.default_island_id,
|
|
island_name="default",
|
|
chain_id=self.default_chain_id,
|
|
status=IslandStatus.ACTIVE,
|
|
joined_at=time.time(),
|
|
is_hub=False
|
|
)
|
|
self.island_peers[self.default_island_id] = set()
|
|
logger.info(f"Initialized with default island: {self.default_island_id}")
|
|
|
|
async def start(self):
|
|
"""Start island manager"""
|
|
self.running = True
|
|
logger.info(f"Starting island manager for node {self.local_node_id}")
|
|
|
|
# Start background tasks
|
|
tasks = [
|
|
asyncio.create_task(self._bridge_request_monitor()),
|
|
asyncio.create_task(self._island_health_check())
|
|
]
|
|
|
|
try:
|
|
await asyncio.gather(*tasks)
|
|
except Exception as e:
|
|
logger.error(f"Island manager error: {e}")
|
|
finally:
|
|
self.running = False
|
|
|
|
async def stop(self):
|
|
"""Stop island manager"""
|
|
self.running = False
|
|
logger.info("Stopping island manager")
|
|
|
|
def join_island(self, island_id: str, island_name: str, chain_id: str, is_hub: bool = False) -> bool:
|
|
"""Join an island"""
|
|
if island_id in self.islands:
|
|
logger.warning(f"Already member of island {island_id}")
|
|
return False
|
|
|
|
self.islands[island_id] = IslandMembership(
|
|
island_id=island_id,
|
|
island_name=island_name,
|
|
chain_id=chain_id,
|
|
status=IslandStatus.ACTIVE,
|
|
joined_at=time.time(),
|
|
is_hub=is_hub
|
|
)
|
|
self.island_peers[island_id] = set()
|
|
|
|
logger.info(f"Joined island {island_id} (name: {island_name}, chain: {chain_id})")
|
|
return True
|
|
|
|
def leave_island(self, island_id: str) -> bool:
|
|
"""Leave an island"""
|
|
if island_id == self.default_island_id:
|
|
logger.warning("Cannot leave default island")
|
|
return False
|
|
|
|
if island_id not in self.islands:
|
|
logger.warning(f"Not member of island {island_id}")
|
|
return False
|
|
|
|
# Remove from active bridges if present
|
|
if island_id in self.active_bridges:
|
|
self.active_bridges.remove(island_id)
|
|
|
|
del self.islands[island_id]
|
|
if island_id in self.island_peers:
|
|
del self.island_peers[island_id]
|
|
|
|
logger.info(f"Left island {island_id}")
|
|
return True
|
|
|
|
def request_bridge(self, target_island_id: str) -> str:
|
|
"""Request bridge to another island"""
|
|
if target_island_id in self.islands:
|
|
logger.warning(f"Already member of island {target_island_id}")
|
|
return ""
|
|
|
|
request_id = str(uuid.uuid4())
|
|
request = BridgeRequest(
|
|
request_id=request_id,
|
|
source_island_id=self.default_island_id,
|
|
target_island_id=target_island_id,
|
|
source_node_id=self.local_node_id,
|
|
timestamp=time.time(),
|
|
status="pending"
|
|
)
|
|
|
|
self.bridge_requests[request_id] = request
|
|
logger.info(f"Requested bridge to island {target_island_id} (request_id: {request_id})")
|
|
|
|
return request_id
|
|
|
|
def approve_bridge_request(self, request_id: str) -> bool:
|
|
"""Approve a bridge request"""
|
|
if request_id not in self.bridge_requests:
|
|
logger.warning(f"Unknown bridge request {request_id}")
|
|
return False
|
|
|
|
request = self.bridge_requests[request_id]
|
|
request.status = "approved"
|
|
|
|
# Add target island to our membership
|
|
self.join_island(request.target_island_id, f"bridge-{request.target_island_id[:8]}", f"bridge-{request.target_island_id[:8]}", is_hub=False)
|
|
|
|
# Mark as active bridge
|
|
self.active_bridges.add(request.target_island_id)
|
|
|
|
logger.info(f"Approved bridge request {request_id} to island {request.target_island_id}")
|
|
return True
|
|
|
|
def reject_bridge_request(self, request_id: str) -> bool:
|
|
"""Reject a bridge request"""
|
|
if request_id not in self.bridge_requests:
|
|
logger.warning(f"Unknown bridge request {request_id}")
|
|
return False
|
|
|
|
request = self.bridge_requests[request_id]
|
|
request.status = "rejected"
|
|
|
|
logger.info(f"Rejected bridge request {request_id} from island {request.source_island_id}")
|
|
return True
|
|
|
|
def get_island_peers(self, island_id: str) -> Set[str]:
|
|
"""Get peers in a specific island"""
|
|
return self.island_peers.get(island_id, set()).copy()
|
|
|
|
def add_island_peer(self, island_id: str, node_id: str):
|
|
"""Add a peer to an island"""
|
|
if island_id not in self.island_peers:
|
|
self.island_peers[island_id] = set()
|
|
|
|
self.island_peers[island_id].add(node_id)
|
|
|
|
# Update peer count
|
|
if island_id in self.islands:
|
|
self.islands[island_id].peer_count = len(self.island_peers[island_id])
|
|
|
|
def remove_island_peer(self, island_id: str, node_id: str):
|
|
"""Remove a peer from an island"""
|
|
if island_id in self.island_peers:
|
|
self.island_peers[island_id].discard(node_id)
|
|
|
|
# Update peer count
|
|
if island_id in self.islands:
|
|
self.islands[island_id].peer_count = len(self.island_peers[island_id])
|
|
|
|
def get_island_info(self, island_id: str) -> Optional[IslandMembership]:
|
|
"""Get information about an island"""
|
|
return self.islands.get(island_id)
|
|
|
|
def get_all_islands(self) -> List[IslandMembership]:
|
|
"""Get all island memberships"""
|
|
return list(self.islands.values())
|
|
|
|
def get_active_bridges(self) -> List[str]:
|
|
"""Get list of active bridge island IDs"""
|
|
return list(self.active_bridges)
|
|
|
|
def get_bridge_requests(self) -> List[BridgeRequest]:
|
|
"""Get all bridge requests"""
|
|
return list(self.bridge_requests.values())
|
|
|
|
def is_member_of_island(self, island_id: str) -> bool:
|
|
"""Check if node is member of an island"""
|
|
return island_id in self.islands
|
|
|
|
def is_bridged_to_island(self, island_id: str) -> bool:
|
|
"""Check if node has active bridge to an island"""
|
|
return island_id in self.active_bridges
|
|
|
|
async def _bridge_request_monitor(self):
|
|
"""Monitor bridge requests and handle timeouts"""
|
|
while self.running:
|
|
try:
|
|
current_time = time.time()
|
|
|
|
# Remove expired requests (older than 1 hour)
|
|
expired_requests = [
|
|
req_id for req_id, req in self.bridge_requests.items()
|
|
if current_time - req.timestamp > 3600 and req.status == "pending"
|
|
]
|
|
|
|
for req_id in expired_requests:
|
|
del self.bridge_requests[req_id]
|
|
logger.info(f"Removed expired bridge request {req_id}")
|
|
|
|
await asyncio.sleep(60) # Check every minute
|
|
|
|
except Exception as e:
|
|
logger.error(f"Bridge request monitor error: {e}")
|
|
await asyncio.sleep(10)
|
|
|
|
async def _island_health_check(self):
|
|
"""Check health of island memberships"""
|
|
while self.running:
|
|
try:
|
|
# Check for inactive islands (no peers for 10 minutes)
|
|
current_time = time.time()
|
|
|
|
for island_id, membership in list(self.islands.items()):
|
|
if island_id == self.default_island_id:
|
|
continue # Don't deactivate default island
|
|
|
|
peer_count = len(self.island_peers.get(island_id, set()))
|
|
|
|
if peer_count == 0 and membership.status == IslandStatus.ACTIVE:
|
|
# Check how long it's been inactive
|
|
if current_time - membership.joined_at > 600: # 10 minutes
|
|
membership.status = IslandStatus.INACTIVE
|
|
logger.warning(f"Island {island_id} marked as inactive (no peers)")
|
|
|
|
await asyncio.sleep(30) # Check every 30 seconds
|
|
|
|
except Exception as e:
|
|
logger.error(f"Island health check error: {e}")
|
|
await asyncio.sleep(10)
|
|
|
|
|
|
# Global island manager instance
|
|
island_manager_instance: Optional[IslandManager] = None
|
|
|
|
|
|
def get_island_manager() -> Optional[IslandManager]:
|
|
"""Get global island manager instance"""
|
|
return island_manager_instance
|
|
|
|
|
|
def create_island_manager(node_id: str, default_island_id: str, default_chain_id: str) -> IslandManager:
|
|
"""Create and set global island manager instance"""
|
|
global island_manager_instance
|
|
island_manager_instance = IslandManager(node_id, default_island_id, default_chain_id)
|
|
return island_manager_instance
|