SPB Git

Repository Public

import-discovery.py 4.7 KB 63 lines python

1#!/usr/bin/env python32"""One-off importer: turns read-only discovery output (sysctl / system_profiler / ifconfig3link speed, collected over SSH on 2026-11-09) into data/nodes.json.4Only public-safe fields are kept: no IP, MAC, serial number, FQDN or location."""5import json, os, re, sys6D = sys.argv[1] if len(sys.argv) > 1 else "/tmp/mlr-discovery"7ORDER = "M3U96a M3U96b M2U64 M4M64a M4M64b M4M64c M5M36 M4BP48 M4BP36 M4M36 M2M32 M2M32b M2M32c m4mc M1M32 m4ma m4mb m4md m4ml m4mm m4mn m6ma m6mb m5ma m2m16 M3BA24 M3BA16 m2m8a m2m8b".split()8ROLES = {9 "M3U96a": ["research-compute", "data", "services"], "M3U96b": ["research-compute", "services"],10 "M2U64": ["services"], "M4M64a": ["services", "monitoring"], "M4M64b": ["services"], "M4M64c": ["services"],11 "M1M32": ["gateway"], "M2M32b": ["services"], "m4mm": ["services"], "m4mn": ["services"], "m6mb": ["services"],12}13ADDED = {"M4M64c": "2026-09-25", "M5M36": "2026-09-26", "m6ma": "2026-09-26", "m6mb": "2026-09-28", "m5ma": "2026-09-28",14         "m4ml": "2026-09-21", "m4mm": "2026-09-21", "m4mn": "2026-09-21", "m4md": "2026-09-04"}15PLACEMENT = {"M4BP36": ("C", "1", ["rack-c"]), "M3BA16": ("C", "2", ["rack-c"]), "M3BA24": ("C", "2", ["rack-c"])}16# Fallbacks from the owner's measured inventory of 2026-09-10 (system_profiler) for machines not reachable on 2026-11-09.17FALLBACK = {"M4BP36": dict(model_name="MacBook Pro", model_id="Mac16,6", chip="Apple M4 Max", ncpu="14", pcores="10", ecores="4", mem_gb="36", gpu_cores="", disk_bytes=str(995*10**9), verified="2026-09-10", lifecycle="offline",18            source="Owner inventory measured with system_profiler on 2026-09-10; machine was powered off during the 2026-11-09 discovery")}19EXTRA = {"M4BP48": dict(model_name="MacBook Pro", gpu_cores="40", disk_bytes="994662584320", link="1000baseT")}20TIERS = {"m6ma": "2S + 4P + 6E", "m6mb": "2S + 4P + 6E", "m5ma": "5S + 10P", "M5M36": "6S + 12P"}21out = []22for i, alias in enumerate(ORDER, 1):23    kv = {}24    p = os.path.join(D, alias + ".txt")25    if os.path.exists(p):26        for line in open(p):27            if "=" in line:28                k, v = line.rstrip("\n").split("=", 1)29                if k == "link":30                    if "1000baseT" in v or "10Gbase" in v or "2500Base" in v: kv["link"] = v31                elif k not in kv: kv[k] = v32    kv.update({k: v for k, v in EXTRA.get(alias, {}).items() if not kv.get(k)})33    fb = FALLBACK.get(alias)34    if fb and kv.get("exit") != "0": kv = dict(fb)35    fam = kv.get("model_name") or None36    fam = fam if fam in ("Mac Studio", "Mac mini", "MacBook Pro", "MacBook Air") else "Other"37    ncpu = int(kv["ncpu"]) if kv.get("ncpu") else None38    pc, ec = kv.get("pcores"), kv.get("ecores")39    layout = TIERS.get(alias) or (f"{pc}P + {ec}E" if pc and ec and int(pc) + int(ec) == ncpu else None)40    disk = round(int(kv["disk_bytes"]) / 1e9) if kv.get("disk_bytes") else None41    link = kv.get("link", "")42    net = "1 GbE Ethernet (1000BASE-T, negotiated link)" if "1000baseT" in link else None43    roles = ROLES.get(alias) or (["portable", "worker"] if fam in ("MacBook Pro", "MacBook Air") else ["worker"])44    rack, shelf, imgs = PLACEMENT.get(alias, (None, None, []))45    out.append({46        "id": f"node-{i:02d}", "number": i, "publicName": alias, "family": fam,47        "modelIdentifier": kv.get("model_id") or None,48        "chip": kv.get("chip") or None, "cpuCores": ncpu, "cpuCoreLayout": layout,49        "gpuCores": int(kv["gpu_cores"]) if kv.get("gpu_cores") else None,50        "unifiedMemoryGB": int(kv["mem_gb"]) if kv.get("mem_gb") else None,51        "storageGB": disk, "networkInterface": net, "role": roles,52        "lifecycle": (fb or {}).get("lifecycle", "active") if kv.get("exit") != "0" and fb else "active",53        "rack": rack, "shelf": shelf, "addedAt": ADDED.get(alias),54        "description": {"en": "", "fr": ""}, "imageIds": imgs,55        "verifiedAt": (fb or {}).get("verified") if kv.get("exit") != "0" and fb else "2026-11-09",56        "source": (fb or {}).get("source") if kv.get("exit") != "0" and fb else "Read-only discovery over SSH on 2026-11-09 (sysctl, system_profiler, ifconfig link speed)",57        "publicVisibility": True,58    })59json.dump(out, open("data/nodes.json", "w"), indent=2, ensure_ascii=False)60print(len(out), "nodes;", sum(n["cpuCores"] or 0 for n in out), "cores;", sum(n["unifiedMemoryGB"] or 0 for n in out), "GB;",61      "missing:", [(n["publicName"], [k for k in ("chip","cpuCores","gpuCores","unifiedMemoryGB","storageGB","networkInterface") if n[k] is None]) for n in out if any(n[k] is None for k in ("chip","cpuCores","gpuCores","unifiedMemoryGB","storageGB","networkInterface"))])62from collections import Counter; print(Counter(n["family"] for n in out), Counter(n["lifecycle"] for n in out))63
Files main
Menu

Theme