SPB Git
4.4 KB · 97 lines python
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
Files · 29a50362078452d2dd01b6b79868199cb58a3d0f
Menu

Theme