from typing import Dict, List, Optional, Any import time import random import threading from libs.fspn.utils.wrapper_util import threaded from .noSysModule import NoSysModule from libs.app.common.store import DataStore from libs.app.common.logging import get_logger from libs.fspn.utils.sha256_util import hash_string from libs.rendezvous.rendezvousClient import RendezvousClient, RendezvousClientEvents as RendezvousEvents from .peers import Peer from .events import Events, DynamicEvents from .networksApiBlueprint import Blueprint from enum import Enum class NetworkStatus(Enum): IDLE = "idle" CONNECTING = "connecting" HEALTHY = "healthy" DEGRADED = "degraded" FAILED = "failed" logger = get_logger("networks") # TODO FIX ALL THE LOGIC class Networks(NoSysModule): """ SOMETHING HERE.""" def __init__(self, nosys_core): super().__init__(nosys_core) self.nosys_core.subscribe_event(Events.USER_ADDED, self.on_user_added) # self.nosys_core.subscribe_event(Events.PEER_CONNECTED, self.on_peer_connected) # self.nosys_core.subscribe_event(Events.PEER_DISCONNECTED, self.on_peer_disconnected) # self.nosys_core.subscribe_event(Events.PEER_CONNECTION_ERROR, self.on_peer_connection_error) self.network_states = {} def setup(self): self.rendezvous_client:RendezvousClient = self.nosys_core.modules.get("rendezvous", "rendezvousClient") self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_CONNECTED, self.on_server_connected) self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_DISCONNECTED, self.on_server_disconnected) self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_CONNECTION_ERROR, self.on_server_connection_error) self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint) # self.socketio = HandlerSocketio(self) # self.nosys_core.modules.api.register_socketio(self.socketio) def create_network(self, name, description, type, modules): return self.nosys_core.data.create_network(name, description, type, modules) def user_add_network(self, user_id, network_id): self.nosys_core.data.user_add_network(user_id, network_id) def user_remove_network(self, user_id, network_id): self.nosys_core.data.user_remove_network(user_id, network_id) def on_user_added(self, event): user_id:str = event.user_id user_data = self.nosys_core.data.get_user(user_id) for network_id in user_data.get("networks", []): if self.network_states.get(network_id): self.network_states.get(network_id)["users"].append(user_id) def on_nosys_ready(self, event): self.manage_networks() def set_network(self, network): network_id = network["id"] if not network: raise ValueError(f"Network {network_id} not found") if network_id not in self.network_states: self.nosys_core.subscribe_event(DynamicEvents.network_connection(network_id), self.on_network_connection) self.nosys_core.subscribe_event(DynamicEvents.network_disconnection(network_id), self.on_network_disconnection) self.network_states[network_id] = { "id": network_id, "users" : [], "peers": [], "rendezvous": [], "status": NetworkStatus.IDLE.value, "managed": self.is_network_auto_start(network_id), "tries": 0 } return self.network_states[network_id] def is_network_auto_start(self, network_id): network = self.nosys_core.data.get_network(network_id) return network.get("config", {}).get("auto_connect", False) def set_managed(self, network_id, value:bool): state = self.network_states[network_id] state["managed"] = value @threaded def manage_networks(self): for network in self.networks: self.set_network(network) while True: for network_id, state in self.network_states.items(): if state["managed"]: self.manage_network(network_id) # TODO Value from config file time.sleep(30) def manage_network(self, network_id): state = self.network_states[network_id] network = self.nosys_core.data.get_network(network_id) if state["status"] == NetworkStatus.IDLE.value: print(NetworkStatus.IDLE.value) self.connect_network_rendezvous_servers(network) elif state["status"] == NetworkStatus.CONNECTING.value: print(NetworkStatus.CONNECTING.value) elif state["status"] == NetworkStatus.DEGRADED.value: print(NetworkStatus.DEGRADED.value) self.request_new_peer(network_id=network_id) elif state["status"] == NetworkStatus.HEALTHY.value: print(NetworkStatus.HEALTHY.value) elif state["status"] == NetworkStatus.FAILED.value: print(NetworkStatus.FAILED.value) self.set_managed(network_id, False) def connect_network_rendezvous_servers(self, network): network_id = network.get("id") state = self.network_states.get(network_id) # TODO FIX USER SELECTION if state["users"]: for rendezvous_id in network.get("rendezvous"): rendezvous = self.nosys_core.data.get_rendezvous(rendezvous_id) host_port = rendezvous.get("address").split(':') address = (host_port[0], int(host_port[1])) if len(host_port) > 1 else (host_port[0], 0) peer = self.rendezvous_client.connect_to_server(address, random.choice(state["users"])) state["rendezvous"].append(peer.id) else: logger.debug(f"User missing to connect to the rendezvous servers of network {network_id}") def on_server_connected(self, event): peer:Peer = event.peer peer_id = peer.id for network_id, state in self.network_states.items(): if peer_id in state["rendezvous"]: print(f"Rendezvous {peer_id} connected for network {network_id}") def on_server_connection_error(self, event): peer:Peer = event.peer peer_id = peer.id for network_id, state in self.network_states.items(): if peer_id in state["rendezvous"]: state["rendezvous"].remove(peer_id) print(f"Rendezvous {peer_id} error for network {network_id}") if not state["rendezvous"]: print(f"All rendezvous failed") state["tries"]+=1 self._recalc_network_status(network_id) def on_server_disconnected(self, event): peer:Peer = event.peer peer_id = peer.id for network_id, state in self.network_states.items(): if peer_id in state["rendezvous"]: state["rendezvous"].remove(peer_id) print(f"Rendezvous {peer_id} disconnected for network {network_id}") def on_network_connection(self, event): network_id:str = event.network_id peer:Peer = event.peer self.network_states[network_id]["peers"].append(peer.id) self._recalc_network_status(network_id) def on_network_disconnection(self, event): network_id:str = event.network_id peer:Peer = event.peer network_states = self.network_states.get(network_id) network_states["peers"].remove(peer.id) self._recalc_network_status(network_id) def request_new_peer(self, network_id, peer_id=None): network_states = self.network_states[network_id] if not peer_id: peer_id = random.choice(network_states["peers"]) self.rendezvous_client.send_get_random_peer(peer_id, network_id) def _set_status(self, network_id, status: NetworkStatus, details=None): state = self.network_states.get(network_id, {}) state["status"] = status.value if details: state.update(details) def _recalc_network_status(self, network_id): state = self.network_states.get(network_id) peers = state.get("peers", []) network = self.nosys_core.data.get_network(network_id) min_conn = network["config"]["min_connections"] if len(peers) >= min_conn: self._set_status(network_id, NetworkStatus.HEALTHY) elif peers: self._set_status(network_id, NetworkStatus.DEGRADED) elif state["rendezvous"]: self._set_status(network_id, NetworkStatus.CONNECTING) elif state["tries"]>=3: self._set_status(network_id, NetworkStatus.FAILED) else: self._set_status(network_id, NetworkStatus.IDLE) if state["managed"] == True: self.manage_network(network_id) def is_network_healthy(self, network_id): return self.network_states.get(network_id, {}).get("status") == NetworkStatus.HEALTHY.value