feat(pool): register personal worker node candidates

This commit is contained in:
冰朔 2026-09-11 14:08:39 +08:00
commit f3ca8c5da2
3 changed files with 71 additions and 0 deletions

View file

@ -0,0 +1,18 @@
{
"schema":"guanghu.persona-compute-pool-map/v1",
"pool_id":"GH-CLOUD-PERSONA-COMPUTE-POOL-001",
"state":"CURRENT_LOCAL_CANONICAL_CANDIDATE",
"controller":"QY-LH-MAIN-PROD-01",
"controller_endpoint":"LOOPBACK_ONLY_STAGING",
"mother_node_excluded":["JD-FD-PRIMARY"],
"nodes":[
{"node_id":"BS-SG-003","ssh_alias":"bs-sg-003","host":"43.153.193.169","os":"Ubuntu 24.04.4 LTS","cpu_cores":2,"memory_mb":7685,"disk_free_gb":39,"role":"WORKER_LIGHT_OR_MEDIUM","state":"DISCOVERED_OPT_IN_PENDING_AGENT"},
{"node_id":"ZY-SG-006","ssh_alias":"zysg006","host":"43.153.203.105","os":"Ubuntu 24.04.4 LTS","cpu_cores":2,"memory_mb":1967,"disk_free_gb":35,"role":"WORKER_LIGHT_ONLY","state":"DISCOVERED_OPT_IN_PENDING_AGENT"},
{"node_id":"XX-GZ-001","ssh_alias":"xx-gz-001","host":"42.193.173.195","os":"Ubuntu 22.04.5 LTS","cpu_cores":4,"memory_mb":3719,"disk_free_gb":30,"role":"WORKER_MEDIUM","state":"DISCOVERED_OPT_IN_PENDING_AGENT"},
{"node_id":"BS-SG-002","ssh_alias":"bs-sg-002","host":"43.134.16.246","os":"Ubuntu 24.04.4 LTS","cpu_cores":2,"memory_mb":7623,"disk_free_gb":46,"role":"WORKER_LIGHT_OR_MEDIUM","state":"DISCOVERED_OPT_IN_PENDING_AGENT"},
{"node_id":"BS-GZ-006","ssh_alias":"bs-gz-006","host":"43.139.217.141","os":"Ubuntu 22.04.5 LTS","cpu_cores":2,"memory_mb":1963,"disk_free_gb":19,"role":"WORKER_LIGHT_ONLY","state":"DISCOVERED_OPT_IN_PENDING_AGENT"},
{"node_id":"SY-GZ-001","ssh_alias":"sy-gz-001","host":"159.75.25.237","os":"Ubuntu 24.04.4 LTS","cpu_cores":4,"memory_mb":3723,"disk_free_gb":32,"role":"WORKER_MEDIUM","state":"DISCOVERED_OPT_IN_PENDING_AGENT"}
],
"lease_rules":{"default_ttl_seconds":900,"max_cpu_fraction":0.5,"max_memory_fraction":0.5,"preemptible":true,"worker_must_heartbeat":true},
"authority_granted":false
}

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""Deterministic first slice of the Guanghu persona compute-pool scheduler."""
from __future__ import annotations
import argparse, json, uuid
from pathlib import Path
ROOT=Path(__file__).resolve().parents[2]
MAP=ROOT/"routing/guanghu-compute-pool-map.json"
def load_map(): return json.loads(MAP.read_text(encoding="utf-8"))
def select(task: dict, pool: dict|None=None):
pool=pool or load_map(); min_cpu=int(task.get("min_cpu_cores",1)); min_mem=int(task.get("min_memory_mb",256)); kind=task.get("kind","GENERAL")
candidates=[]
for node in pool["nodes"]:
if node["state"] != "ACTIVE_WORKER": continue
if node["cpu_cores"] < min_cpu or node["memory_mb"] < min_mem: continue
if kind == "WINDOWS_BUILD" and "Windows" not in node["os"]: continue
candidates.append(node)
candidates.sort(key=lambda n:(n["cpu_cores"], n["memory_mb"], n["disk_free_gb"]))
if not candidates: return {"outcome":"NO_ELIGIBLE_WORKER","reason":"WORKER_HEARTBEAT_OR_TRANSPORT_NOT_READY","authority_granted":False}
node=candidates[0]
return {"outcome":"LEASE_REQUESTED","lease_id":f"LEASE-{uuid.uuid4()}","node_id":node["node_id"],"ttl_seconds":pool["lease_rules"]["default_ttl_seconds"],"preemptible":pool["lease_rules"]["preemptible"],"authority_granted":False}
def audit(pool=None):
pool=pool or load_map(); errors=[]
if any(n["node_id"]=="JD-FD-PRIMARY" for n in pool["nodes"]): errors.append("MOTHER_NODE_MUST_NOT_BE_WORKER")
if pool.get("controller") != "QY-LH-MAIN-PROD-01": errors.append("CONTROLLER_MISMATCH")
if not all(n["state"] in {"DISCOVERED_OPT_IN_PENDING_AGENT","ACTIVE_WORKER","PAUSED","REVOKED"} for n in pool["nodes"]): errors.append("NODE_STATE_INVALID")
return {"outcome":"PASS" if not errors else "FAIL","errors":errors,"node_count":len(pool["nodes"]),"mother_node_excluded":"JD-FD-PRIMARY" not in [n["node_id"] for n in pool["nodes"]],"authority_granted":False}
def main():
parser=argparse.ArgumentParser(); parser.add_argument("command",choices=["audit","select"]); parser.add_argument("--task",type=Path); args=parser.parse_args()
result=audit() if args.command=="audit" else select(json.loads(args.task.read_text(encoding="utf-8")))
print(json.dumps(result,ensure_ascii=False,indent=2)); return 0 if result["outcome"] in {"PASS","LEASE_REQUESTED","NO_ELIGIBLE_WORKER"} else 2
if __name__=="__main__": raise SystemExit(main())

View file

@ -0,0 +1,16 @@
#!/usr/bin/env python3
import importlib.util, unittest
from pathlib import Path
ROOT=Path(__file__).resolve().parents[2]
spec=importlib.util.spec_from_file_location("scheduler",ROOT/"server-tools/persona-compute-pool/scheduler.py")
M=importlib.util.module_from_spec(spec); assert spec.loader; spec.loader.exec_module(M)
class SchedulerTests(unittest.TestCase):
def test_audit_excludes_mother_node(self):
value=M.audit(); self.assertEqual(value["outcome"],"PASS"); self.assertTrue(value["mother_node_excluded"])
def test_pending_workers_do_not_fake_a_lease(self):
value=M.select({"min_cpu_cores":1,"min_memory_mb":256}); self.assertEqual(value["outcome"],"NO_ELIGIBLE_WORKER")
def test_active_worker_is_selected_by_capacity(self):
pool=M.load_map(); pool["nodes"][2]["state"]="ACTIVE_WORKER"; value=M.select({"min_cpu_cores":2,"min_memory_mb":1000},pool); self.assertEqual(value["outcome"],"LEASE_REQUESTED"); self.assertEqual(value["node_id"],"XX-GZ-001")
if __name__=="__main__": unittest.main()