From f3ca8c5da2f5b3a6ca1dfd94fe4eec2fbacd3a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Fri, 11 Sep 2026 14:08:39 +0800 Subject: [PATCH] feat(pool): register personal worker node candidates --- routing/guanghu-compute-pool-map.json | 18 +++++++++ .../persona-compute-pool/scheduler.py | 37 +++++++++++++++++++ .../persona-compute-pool/test_scheduler.py | 16 ++++++++ 3 files changed, 71 insertions(+) create mode 100644 routing/guanghu-compute-pool-map.json create mode 100644 server-tools/persona-compute-pool/scheduler.py create mode 100644 server-tools/persona-compute-pool/test_scheduler.py diff --git a/routing/guanghu-compute-pool-map.json b/routing/guanghu-compute-pool-map.json new file mode 100644 index 0000000..40c49a9 --- /dev/null +++ b/routing/guanghu-compute-pool-map.json @@ -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 +} diff --git a/server-tools/persona-compute-pool/scheduler.py b/server-tools/persona-compute-pool/scheduler.py new file mode 100644 index 0000000..c5e72d0 --- /dev/null +++ b/server-tools/persona-compute-pool/scheduler.py @@ -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()) diff --git a/server-tools/persona-compute-pool/test_scheduler.py b/server-tools/persona-compute-pool/test_scheduler.py new file mode 100644 index 0000000..56c9356 --- /dev/null +++ b/server-tools/persona-compute-pool/test_scheduler.py @@ -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()