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

359 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
network_breathing.py Дыхание сети Montana
Книга Монтана, Глава 08:
> "Каждые двенадцать секунд — синхронизация. Git pull. Git push. Вдох — выдох."
> "Пять узлов. Один организм. Одно дыхание."
> "Watchdog. Сервис, который следит за всеми.
> Если кто-то отстал подтягивает. Если кто-то упал поднимает тревогу."
Дыхание сети:
- 12 секунд цикл
- Pull = вдох (получить данные)
- Push = выдох (отдать данные)
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Dict, List, Optional, Set
from enum import Enum
import hashlib
import time
class BreathPhase(Enum):
"""Фаза дыхания."""
INHALE = "inhale" # Вдох — pull
EXHALE = "exhale" # Выдох — push
PAUSE = "pause" # Пауза между
class NodeSyncStatus(Enum):
"""Статус синхронизации узла."""
SYNCED = "synced"
BEHIND = "behind"
AHEAD = "ahead"
DISCONNECTED = "disconnected"
@dataclass
class SyncState:
"""Состояние синхронизации узла."""
node_id: str
last_commit: str
last_sync: str
status: NodeSyncStatus
commits_behind: int = 0
commits_ahead: int = 0
@dataclass
class BreathCycle:
"""Один цикл дыхания сети."""
cycle_id: int
started_at: str
completed_at: Optional[str] = None
phase: BreathPhase = BreathPhase.INHALE
nodes_synced: int = 0
nodes_failed: int = 0
data_transferred_bytes: int = 0
class NetworkBreathing:
"""
Дыхание сети синхронизация каждые 12 секунд.
Книга Монтана:
> "Видишь? Москва только что обновилась.
> Через секунду Амстердам получит те же данные.
> Потом Алматы. Петербург. Новосибирск."
"""
# Константы из книги
BREATH_INTERVAL_SEC = 12 # 12 секунд между циклами
INHALE_DURATION_SEC = 4 # Вдох
EXHALE_DURATION_SEC = 4 # Выдох
PAUSE_DURATION_SEC = 4 # Пауза
def __init__(self, nodes: List[str] = None):
self.nodes = nodes or ["moscow", "amsterdam", "almaty", "spb", "novosibirsk"]
self.node_states: Dict[str, SyncState] = {}
self.cycles: List[BreathCycle] = []
self.current_cycle: Optional[BreathCycle] = None
self.is_breathing = False
self.cycle_count = 0
# Инициализация состояний узлов
for node in self.nodes:
self.node_states[node] = SyncState(
node_id=node,
last_commit=self._generate_commit_hash(),
last_sync=datetime.now(timezone.utc).isoformat(),
status=NodeSyncStatus.SYNCED
)
def _generate_commit_hash(self) -> str:
"""Сгенерировать hash коммита."""
data = f"{time.time()}:{id(self)}"
return hashlib.sha256(data.encode()).hexdigest()[:8]
def start_breathing(self) -> None:
"""Начать дыхание сети."""
self.is_breathing = True
def stop_breathing(self) -> None:
"""Остановить дыхание сети."""
self.is_breathing = False
def inhale(self) -> Dict[str, bool]:
"""
Вдох pull данные от всех узлов.
Returns:
Результат pull для каждого узла
"""
results = {}
for node_id in self.nodes:
state = self.node_states[node_id]
if state.status == NodeSyncStatus.DISCONNECTED:
results[node_id] = False
continue
# Симуляция pull
state.last_sync = datetime.now(timezone.utc).isoformat()
results[node_id] = True
return results
def exhale(self) -> Dict[str, bool]:
"""
Выдох push данные ко всем узлам.
Returns:
Результат push для каждого узла
"""
results = {}
new_commit = self._generate_commit_hash()
for node_id in self.nodes:
state = self.node_states[node_id]
if state.status == NodeSyncStatus.DISCONNECTED:
results[node_id] = False
continue
# Симуляция push
state.last_commit = new_commit
state.last_sync = datetime.now(timezone.utc).isoformat()
state.status = NodeSyncStatus.SYNCED
results[node_id] = True
return results
def breath_cycle(self) -> BreathCycle:
"""
Выполнить один полный цикл дыхания.
Returns:
BreathCycle
"""
self.cycle_count += 1
cycle = BreathCycle(
cycle_id=self.cycle_count,
started_at=datetime.now(timezone.utc).isoformat(),
phase=BreathPhase.INHALE
)
self.current_cycle = cycle
# Вдох
inhale_results = self.inhale()
cycle.phase = BreathPhase.PAUSE
# Выдох
exhale_results = self.exhale()
cycle.phase = BreathPhase.EXHALE
# Подсчёт результатов
cycle.nodes_synced = sum(1 for r in exhale_results.values() if r)
cycle.nodes_failed = sum(1 for r in exhale_results.values() if not r)
cycle.completed_at = datetime.now(timezone.utc).isoformat()
self.cycles.append(cycle)
self.current_cycle = None
return cycle
def watchdog_check(self) -> Dict:
"""
Watchdog проверка всех узлов.
Книга Монтана:
> "Watchdog. Сервис, который следит за всеми."
Returns:
Отчёт watchdog
"""
alerts = []
healthy = []
behind = []
for node_id, state in self.node_states.items():
if state.status == NodeSyncStatus.DISCONNECTED:
alerts.append({
"node": node_id,
"alert": "disconnected",
"last_seen": state.last_sync
})
elif state.status == NodeSyncStatus.BEHIND:
behind.append({
"node": node_id,
"commits_behind": state.commits_behind
})
else:
healthy.append(node_id)
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"healthy_nodes": healthy,
"behind_nodes": behind,
"alerts": alerts,
"total_nodes": len(self.nodes),
"health_percentage": len(healthy) / len(self.nodes) * 100
}
def disconnect_node(self, node_id: str) -> None:
"""Симулировать отключение узла."""
if node_id in self.node_states:
self.node_states[node_id].status = NodeSyncStatus.DISCONNECTED
def reconnect_node(self, node_id: str) -> None:
"""Восстановить узел."""
if node_id in self.node_states:
state = self.node_states[node_id]
state.status = NodeSyncStatus.BEHIND
state.commits_behind = self.cycle_count # Примерно
def get_breathing_status(self) -> Dict:
"""Статус дыхания сети."""
return {
"is_breathing": self.is_breathing,
"cycle_interval_sec": self.BREATH_INTERVAL_SEC,
"total_cycles": self.cycle_count,
"current_phase": self.current_cycle.phase.value if self.current_cycle else "idle",
"nodes": len(self.nodes),
"last_cycle": self.cycles[-1].completed_at if self.cycles else None,
"metaphor": "inhale (pull) → pause → exhale (push)"
}
class Watchdog:
"""
Watchdog сервис наблюдения за сетью.
Книга Монтана:
> "Если кто-то отстал — подтягивает.
> Если кто-то упал поднимает тревогу."
"""
def __init__(self, breathing: NetworkBreathing):
self.breathing = breathing
self.alerts: List[Dict] = []
self.is_running = False
def check(self) -> Dict:
"""Выполнить проверку."""
report = self.breathing.watchdog_check()
# Сохранить алерты
if report["alerts"]:
self.alerts.extend(report["alerts"])
return report
def auto_heal(self) -> int:
"""
Автоматическое восстановление отставших узлов.
Returns:
Количество восстановленных узлов
"""
healed = 0
for node_id, state in self.breathing.node_states.items():
if state.status == NodeSyncStatus.BEHIND:
# Форсированная синхронизация
state.last_commit = self.breathing._generate_commit_hash()
state.last_sync = datetime.now(timezone.utc).isoformat()
state.status = NodeSyncStatus.SYNCED
state.commits_behind = 0
healed += 1
return healed
# ═══════════════════════════════════════════════════════════════════════════════
# DEMO
# ═══════════════════════════════════════════════════════════════════════════════
if __name__ == "__main__":
breathing = NetworkBreathing()
watchdog = Watchdog(breathing)
print("=" * 60)
print("NETWORK BREATHING — Дыхание сети")
print("=" * 60)
print("\n'Каждые 12 секунд — синхронизация. Вдох — выдох.'")
# Статус дыхания
print("\n--- НАСТРОЙКИ ---")
status = breathing.get_breathing_status()
print(f"Интервал: {status['cycle_interval_sec']} секунд")
print(f"Узлов: {status['nodes']}")
print(f"Метафора: {status['metaphor']}")
# Несколько циклов дыхания
print("\n--- ЦИКЛЫ ДЫХАНИЯ ---")
for i in range(3):
cycle = breathing.breath_cycle()
print(f"\nЦикл #{cycle.cycle_id}")
print(f" Фаза: INHALE → PAUSE → EXHALE")
print(f" Синхронизировано: {cycle.nodes_synced}/{len(breathing.nodes)}")
print(f" Время: {cycle.started_at[:19]}{cycle.completed_at[:19]}")
# Watchdog проверка
print("\n--- WATCHDOG ---")
report = watchdog.check()
print(f"Здоровые узлы: {', '.join(report['healthy_nodes'])}")
print(f"Здоровье сети: {report['health_percentage']:.0f}%")
# Симуляция отключения
print("\n--- СИМУЛЯЦИЯ ОТКЛЮЧЕНИЯ SPB ---")
breathing.disconnect_node("spb")
report = watchdog.check()
print(f"Алертов: {len(report['alerts'])}")
if report["alerts"]:
for alert in report["alerts"]:
print(f" ⚠️ {alert['node']}: {alert['alert']}")
# Автовосстановление
print("\n--- АВТОВОССТАНОВЛЕНИЕ ---")
breathing.reconnect_node("spb")
healed = watchdog.auto_heal()
print(f"Восстановлено узлов: {healed}")
# Финальный статус
print("\n--- ФИНАЛЬНЫЙ СТАТУС ---")
report = watchdog.check()
print(f"Здоровье сети: {report['health_percentage']:.0f}%")
print(f"Всего циклов: {breathing.cycle_count}")
print("\n" + "=" * 60)
print("'Пять узлов. Один организм. Одно дыхание.'")
print("=" * 60)