feat(pool): register personal worker node candidates
This commit is contained in:
parent
bb5a7a2f61
commit
f3ca8c5da2
3 changed files with 71 additions and 0 deletions
18
routing/guanghu-compute-pool-map.json
Normal file
18
routing/guanghu-compute-pool-map.json
Normal 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
|
||||
}
|
||||
37
server-tools/persona-compute-pool/scheduler.py
Normal file
37
server-tools/persona-compute-pool/scheduler.py
Normal 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())
|
||||
16
server-tools/persona-compute-pool/test_scheduler.py
Normal file
16
server-tools/persona-compute-pool/test_scheduler.py
Normal 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()
|
||||
Loading…
Reference in a new issue