montana/Русский/Сеть/five_nodes.py

314 lines
11 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
five_nodes.py Топология пяти узлов Montana
Книга Монтана, Глава 08:
> "Пять городов. Три страны. Один организм."
> "1 активный + 1 мозг + 3 зеркала = 5 узлов."
> "Сеть не умирает. Сеть — бессмертна. Пока есть хотя бы один узел — Юнона жива."
Brain-Body Separation:
> "Мозг не должен отвечать на каждое сообщение напрямую.
> Мозг думает. Тело действует."
Архитектура:
- Amsterdam (BOT) активный, принимает сообщения
- Moscow (BRAIN) мозг, принимает решения
- Almaty, SPB, Novosibirsk зеркала (standby)
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Dict, List, Optional
from enum import Enum
import hashlib
class NodeRole(Enum):
"""Роль узла в сети."""
BRAIN = "brain" # Мозг — принимает решения
BOT = "bot" # Бот — обрабатывает сообщения
STANDBY = "standby" # Зеркало — ждёт своей очереди
class NodeStatus(Enum):
"""Статус узла."""
ACTIVE = "active" # Работает
STANDBY = "standby" # В режиме ожидания
FAILED = "failed" # Упал
SYNCING = "syncing" # Синхронизируется
@dataclass
class Node:
"""Узел сети Montana."""
node_id: str
city: str
country: str
role: NodeRole
status: NodeStatus = NodeStatus.STANDBY
last_heartbeat: str = ""
priority: int = 0 # Приоритет в failover цепи (0 = высший)
def heartbeat(self) -> None:
"""Обновить heartbeat."""
self.last_heartbeat = datetime.now(timezone.utc).isoformat()
def to_dict(self) -> dict:
return {
"node_id": self.node_id,
"city": self.city,
"country": self.country,
"role": self.role.value,
"status": self.status.value,
"last_heartbeat": self.last_heartbeat,
"priority": self.priority
}
@dataclass
class NetworkTopology:
"""
Топология сети из 5 узлов.
Формула: 1 active + 1 brain + 3 mirrors = 5 nodes
"""
nodes: Dict[str, Node] = field(default_factory=dict)
brain_id: Optional[str] = None
bot_id: Optional[str] = None
@property
def active_nodes(self) -> List[Node]:
"""Активные узлы."""
return [n for n in self.nodes.values() if n.status == NodeStatus.ACTIVE]
@property
def standby_nodes(self) -> List[Node]:
"""Узлы в режиме ожидания."""
return sorted(
[n for n in self.nodes.values() if n.status == NodeStatus.STANDBY],
key=lambda x: x.priority
)
class FiveNodesNetwork:
"""
Сеть из пяти узлов Montana.
Книга Монтана:
> "Мы разбросаны по карте — но мы одно целое.
> Как нейроны в мозгу. Каждый на своём месте
> но все вместе образуют нечто большее."
"""
def __init__(self):
self.topology = NetworkTopology()
self._init_genesis_nodes()
def _init_genesis_nodes(self) -> None:
"""Инициализировать genesis узлы."""
# Genesis топология из книги
genesis_nodes = [
("amsterdam", "Amsterdam", "Netherlands", NodeRole.BOT, 0),
("moscow", "Moscow", "Russia", NodeRole.BRAIN, 1),
("almaty", "Almaty", "Kazakhstan", NodeRole.STANDBY, 2),
("spb", "Saint Petersburg", "Russia", NodeRole.STANDBY, 3),
("novosibirsk", "Novosibirsk", "Russia", NodeRole.STANDBY, 4),
]
for node_id, city, country, role, priority in genesis_nodes:
node = Node(
node_id=node_id,
city=city,
country=country,
role=role,
priority=priority
)
if role == NodeRole.BOT:
node.status = NodeStatus.ACTIVE
self.topology.bot_id = node_id
elif role == NodeRole.BRAIN:
node.status = NodeStatus.ACTIVE
self.topology.brain_id = node_id
else:
node.status = NodeStatus.STANDBY
node.heartbeat()
self.topology.nodes[node_id] = node
def get_brain(self) -> Optional[Node]:
"""Получить узел-мозг."""
if self.topology.brain_id:
return self.topology.nodes.get(self.topology.brain_id)
return None
def get_bot(self) -> Optional[Node]:
"""Получить активный бот-узел."""
if self.topology.bot_id:
return self.topology.nodes.get(self.topology.bot_id)
return None
def process_message(self, message: str, sender_id: str) -> Dict:
"""
Обработать сообщение через Brain-Body separation.
1. BOT принимает сообщение
2. BRAIN принимает решение
3. BOT отправляет ответ
Args:
message: Текст сообщения
sender_id: ID отправителя
Returns:
Результат обработки
"""
bot = self.get_bot()
brain = self.get_brain()
if not bot or not brain:
return {"error": "Network not ready"}
# BOT принимает
received_at = datetime.now(timezone.utc).isoformat()
bot.heartbeat()
# BRAIN думает (симуляция)
brain.heartbeat()
decision = self._brain_decide(message)
# BOT отвечает
return {
"received_by": bot.node_id,
"processed_by": brain.node_id,
"received_at": received_at,
"decision": decision,
"response": f"Processed by {brain.city}, delivered by {bot.city}"
}
def _brain_decide(self, message: str) -> str:
"""Мозг принимает решение (симуляция)."""
# Простая логика для демо
if "?" in message:
return "question_detected"
elif "!" in message:
return "exclamation_detected"
else:
return "statement_detected"
def node_failed(self, node_id: str) -> Optional[Node]:
"""
Обработать падение узла.
Args:
node_id: ID упавшего узла
Returns:
Узел, который подхватил роль (или None)
"""
if node_id not in self.topology.nodes:
return None
failed_node = self.topology.nodes[node_id]
failed_node.status = NodeStatus.FAILED
failed_role = failed_node.role
# Найти замену из standby
for standby in self.topology.standby_nodes:
if standby.status == NodeStatus.STANDBY:
# Промоутим standby
standby.role = failed_role
standby.status = NodeStatus.ACTIVE
standby.heartbeat()
if failed_role == NodeRole.BOT:
self.topology.bot_id = standby.node_id
elif failed_role == NodeRole.BRAIN:
self.topology.brain_id = standby.node_id
return standby
return None
def get_network_status(self) -> Dict:
"""Статус всей сети."""
active = self.topology.active_nodes
standby = self.topology.standby_nodes
failed = [n for n in self.topology.nodes.values() if n.status == NodeStatus.FAILED]
return {
"total_nodes": len(self.topology.nodes),
"active_nodes": len(active),
"standby_nodes": len(standby),
"failed_nodes": len(failed),
"brain": self.topology.brain_id,
"bot": self.topology.bot_id,
"countries": list(set(n.country for n in self.topology.nodes.values())),
"cities": [n.city for n in self.topology.nodes.values()],
"is_alive": len(active) > 0,
"formula": "1 active + 1 brain + 3 mirrors = 5 nodes"
}
def get_failover_chain(self) -> List[str]:
"""Цепочка failover."""
return [n.node_id for n in sorted(
self.topology.nodes.values(),
key=lambda x: x.priority
)]
# ═══════════════════════════════════════════════════════════════════════════════
# DEMO
# ═══════════════════════════════════════════════════════════════════════════════
if __name__ == "__main__":
network = FiveNodesNetwork()
print("=" * 60)
print("FIVE NODES TOPOLOGY — Топология пяти узлов")
print("=" * 60)
print("\n'Пять городов. Три страны. Один организм.'")
# Статус сети
print("\n--- СТАТУС СЕТИ ---")
status = network.get_network_status()
print(f"Всего узлов: {status['total_nodes']}")
print(f"Активных: {status['active_nodes']}")
print(f"Standby: {status['standby_nodes']}")
print(f"BRAIN: {status['brain']}")
print(f"BOT: {status['bot']}")
print(f"Страны: {', '.join(status['countries'])}")
print(f"Города: {', '.join(status['cities'])}")
# Failover chain
print("\n--- FAILOVER CHAIN ---")
chain = network.get_failover_chain()
print("".join(chain))
# Brain-Body separation
print("\n--- BRAIN-BODY SEPARATION ---")
result = network.process_message("Как дела?", "user_123")
print(f"Получено: {result['received_by']}")
print(f"Обработано: {result['processed_by']}")
print(f"Решение: {result['decision']}")
# Симуляция падения
print("\n--- СИМУЛЯЦИЯ ПАДЕНИЯ AMSTERDAM ---")
replacement = network.node_failed("amsterdam")
if replacement:
print(f"Amsterdam упал!")
print(f"Замена: {replacement.city} ({replacement.node_id})")
print(f"Новый BOT: {network.topology.bot_id}")
# Новый статус
print("\n--- НОВЫЙ СТАТУС ---")
status = network.get_network_status()
print(f"Активных: {status['active_nodes']}")
print(f"Упавших: {status['failed_nodes']}")
print(f"Сеть жива: {'ДА' if status['is_alive'] else 'НЕТ'}")
print("\n" + "=" * 60)
print("'Сеть не умирает. Сеть — бессмертна.'")
print("=" * 60)