spb/maclustr-dispatch
Public
1"""Sélection du nœud le plus efficient pour une app (ou un lot d'apps : plan)."""2from . import config, registry345def eligible(node, m, projected, exclude=()):6 """Retourne (ok, raison)."""7 a = node["alias"]8 pin = m["placement"].get("pin")9 if node.get("role") != "worker" and not (pin == a):10 return False, "rôle %s" % node.get("role")11 if not node.get("online"):12 return False, "hors ligne"13 if a in exclude or a in (m["placement"].get("avoid") or []):14 return False, "exclu"15 if pin and pin != a:16 return False, "épinglé sur %s" % pin17 missing = [r for r in m["requires"]["runtimes"] if not node["runtimes"].get(r)]18 if missing:19 return False, "runtimes manquants : %s" % ",".join(missing)20 need = max(float(m["requires"].get("ram_gb") or 1), m.get("ram_mb_observed", 512) * 1.5 / 1024)21 free = node["free_gb"] - projected[a]["ram_gb"]22 if free - need < 2.0:23 return False, "RAM insuffisante (%.1f G libres, besoin %.1f G)" % (free, need)24 if node["disk_free_gb"] < (m.get("size_mb", 0) / 1024) * 1.5 + 20:25 return False, "disque insuffisant"26 busy = set(node.get("ports", [])) | projected[a]["ports"]27 if not _is_home(a, m):28 for p in m["requires"].get("ports") or []:29 if p in busy:30 return False, "port %s occupé" % p31 return True, ""323334def _is_home(alias, m):35 """Le nœud héberge déjà cette app (source d'import ou registre) : ses propres ports ne la bloquent pas."""36 return alias == m.get("source_node") or alias == registry.node_of(m["app"])373839STAY_BONUS = 0.05 # évite de déplacer des Go de données pour un gain de score marginal404142def score(node, m, projected):43 a = node["alias"]44 need = max(float(m["requires"].get("ram_gb") or 1), m.get("ram_mb_observed", 512) * 1.5 / 1024)45 free_after = node["free_gb"] - projected[a]["ram_gb"] - need46 ram_ratio = max(0.0, free_after / max(node["ram_gb"], 1))47 idle = max(0.0, 1.0 - (node["load1"] + 0.5 * projected[a]["apps"]) / max(node["cores"], 1))48 disk = min(1.0, node["disk_free_gb"] / 500.0)49 cores = min(1.0, node["cores"] / 32.0)50 hosted = len(registry.apps_on(a)) + projected[a]["apps"]51 s = 0.45 * ram_ratio + 0.25 * idle + 0.10 * disk + 0.15 * cores - 0.04 * hosted52 if m["placement"].get("prefer") == a:53 s += 0.1554 if _is_home(a, m):55 s += STAY_BONUS56 return round(s, 4)575859def rank(m, scan, projected=None, exclude=()):60 projected = projected or empty_projection(scan)61 rows = []62 for a, node in scan.items():63 ok, why = eligible(node, m, projected, exclude)64 rows.append({"node": a, "ok": ok, "why": why, "score": score(node, m, projected) if ok else -1})65 rows.sort(key=lambda r: (-r["ok"], -r["score"]))66 return rows676869def empty_projection(scan):70 return {a: {"ram_gb": 0.0, "apps": 0, "ports": set()} for a in scan}717273def plan(manifests, scan, exclude=()):74 """Placement glouton : apps les plus lourdes d'abord, projection des ressources consommées."""75 projected = empty_projection(scan)76 order = sorted(manifests.values(), key=lambda m: -(m.get("ram_mb_observed", 512) + m.get("size_mb", 0) / 20.0))77 out = []78 for m in order:79 rows = rank(m, scan, projected, exclude)80 best = rows[0] if rows and rows[0]["ok"] else None81 if best:82 a = best["node"]83 need = max(float(m["requires"].get("ram_gb") or 1), m.get("ram_mb_observed", 512) * 1.5 / 1024)84 projected[a]["ram_gb"] += need85 projected[a]["apps"] += 186 projected[a]["ports"] |= set(m["requires"].get("ports") or [])87 pin = m["placement"].get("pin")88 blockers = []89 if not best:90 cand = [r for r in rows if not r["ok"] and (r["node"] == pin if pin else (scan[r["node"]].get("online") and scan[r["node"]].get("role") == "worker"))]91 cand.sort(key=lambda r: -(scan[r["node"]].get("ram_gb") or 0))92 blockers = [(r["node"], r["why"]) for r in cand][:8]93 out.append({"app": m["app"], "node": best["node"] if best else None, "score": best["score"] if best else None,94 "current": registry.node_of(m["app"]) or m.get("source_node"), "pin": pin, "reason": m["placement"].get("reason", ""),95 "size_mb": m.get("size_mb", 0), "alternatives": [(r["node"], r["score"]) for r in rows[1:4] if r["ok"]], "blockers": blockers})96 return out97