jean-router: ajout Wake-on-LAN
- Detection si Jean/Ollama inaccessible - Envoi magic packet WoL vers TARGET_MAC (fallback PC_GAMING_MAC) - Attente WAKE_TIMEOUT secondes que la machine demarre - Integration dans get_presets, get_ollama_models, chat_completions - Verrous pour eviter les WoL simultanes - Active via ENABLE_WOL=true
This commit is contained in:
@@ -9,6 +9,7 @@ import os
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import socket
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
@@ -30,6 +31,12 @@ SWITCH_TIMEOUT = int(os.getenv("SWITCH_TIMEOUT", "90"))
|
||||
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "2"))
|
||||
JEAN_STOP_TIMEOUT = int(os.getenv("JEAN_STOP_TIMEOUT", "30"))
|
||||
OLLAMA_UNLOAD_TIMEOUT = int(os.getenv("OLLAMA_UNLOAD_TIMEOUT", "15"))
|
||||
ENABLE_WOL = os.getenv("ENABLE_WOL", "false").lower() == "true"
|
||||
TARGET_MAC = os.getenv("TARGET_MAC", os.getenv("PC_GAMING_MAC", ""))
|
||||
BROADCAST_IP = os.getenv("BROADCAST_IP", "192.168.1.255")
|
||||
WAKE_TIMEOUT = int(os.getenv("WAKE_TIMEOUT", "120"))
|
||||
wol_in_progress = threading.Lock()
|
||||
jean_alive_cache = {"alive": False, "timestamp": 0}
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -65,10 +72,83 @@ def get_presets(force_refresh=False):
|
||||
return presets_cache["data"]
|
||||
except Exception as e:
|
||||
log.warning(f"[Presets] Erreur récupération: {e}")
|
||||
if ENABLE_WOL and (not presets_cache["data"] or force_refresh):
|
||||
wait_for_jean(trigger_wol=True)
|
||||
try:
|
||||
r = requests.get(f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/presets", timeout=5)
|
||||
if r.ok:
|
||||
presets_cache["data"] = r.json()
|
||||
presets_cache["timestamp"] = now
|
||||
return presets_cache["data"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return presets_cache["data"]
|
||||
|
||||
|
||||
def send_wol():
|
||||
"""Envoie un magic packet WoL vers TARGET_MAC"""
|
||||
if not TARGET_MAC:
|
||||
log.warning("[WoL] TARGET_MAC non defini, WoL desactive")
|
||||
return
|
||||
|
||||
mac = TARGET_MAC.replace(":", "").replace("-", "")
|
||||
if len(mac) != 12:
|
||||
log.error(f"[WoL] MAC invalide: {TARGET_MAC}")
|
||||
return
|
||||
|
||||
data = bytes.fromhex("FF" * 6 + mac * 16)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
for i in range(3):
|
||||
s.sendto(data, (BROADCAST_IP, 9))
|
||||
log.info(f"[WoL] Magic packet #{i+1} envoye vers {TARGET_MAC}")
|
||||
|
||||
|
||||
def jean_alive() -> bool:
|
||||
"""Verifie si Jean est accessible"""
|
||||
now = time.time()
|
||||
if now - jean_alive_cache["timestamp"] < 5:
|
||||
return jean_alive_cache["alive"]
|
||||
|
||||
try:
|
||||
url = f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/presets"
|
||||
r = requests.get(url, timeout=3)
|
||||
alive = r.ok
|
||||
except Exception:
|
||||
alive = False
|
||||
|
||||
jean_alive_cache["alive"] = alive
|
||||
jean_alive_cache["timestamp"] = now
|
||||
return alive
|
||||
|
||||
|
||||
def wait_for_jean(trigger_wol=True):
|
||||
"""Attend que Jean soit accessible. Si ENABLE_WOL et trigger_wol, envoie WoL d'abord."""
|
||||
if jean_alive():
|
||||
return
|
||||
|
||||
if not ENABLE_WOL or not trigger_wol:
|
||||
return
|
||||
|
||||
with wol_in_progress:
|
||||
if jean_alive():
|
||||
return
|
||||
|
||||
log.info("[WoL] Jean inaccessible, envoi WoL...")
|
||||
send_wol()
|
||||
|
||||
deadline = time.time() + WAKE_TIMEOUT
|
||||
while time.time() < deadline:
|
||||
if jean_alive():
|
||||
jean_alive_cache["timestamp"] = 0
|
||||
log.info(f"[WoL] Jean UP apres {int(time.time() - deadline + WAKE_TIMEOUT)}s")
|
||||
return
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
|
||||
log.error(f"[WoL] Jean ne repond pas apres {WAKE_TIMEOUT}s")
|
||||
|
||||
|
||||
def get_ollama_models(force_refresh=False):
|
||||
"""Récupère la liste des modèles depuis Ollama"""
|
||||
if not ENABLE_OLLAMA:
|
||||
@@ -88,6 +168,17 @@ def get_ollama_models(force_refresh=False):
|
||||
return data
|
||||
except Exception as e:
|
||||
log.warning(f"[Ollama] Erreur récupération: {e}")
|
||||
if ENABLE_WOL and (not ollama_cache["data"] or force_refresh):
|
||||
wait_for_jean(trigger_wol=True)
|
||||
try:
|
||||
r = requests.get(f"http://{OLLAMA_PROXY_HOST}:{OLLAMA_PROXY_PORT}/api/tags", timeout=5)
|
||||
if r.ok:
|
||||
data = r.json().get("models", [])
|
||||
ollama_cache["data"] = data
|
||||
ollama_cache["timestamp"] = now
|
||||
return data
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ollama_cache["data"]
|
||||
|
||||
@@ -667,6 +758,8 @@ def chat_completions():
|
||||
if model_id in jean_preset_ids or model_id in jean_names:
|
||||
internal_id = jean_name_to_id.get(model_id, model_id)
|
||||
|
||||
wait_for_jean(trigger_wol=True)
|
||||
|
||||
if not ensure_vram_for_backend("jean"):
|
||||
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503
|
||||
|
||||
@@ -695,6 +788,7 @@ def chat_completions():
|
||||
upstream_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/chat/completions"
|
||||
log.info(f"[Router] → Jean ({model_id})")
|
||||
elif ENABLE_OLLAMA:
|
||||
wait_for_jean(trigger_wol=True)
|
||||
ollama_models = get_ollama_models()
|
||||
ollama_ids = [m.get("name", "").replace(":latest", "") for m in ollama_models]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user