控制台改为选项卡,并支持封禁节点与下发计算任务。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-11 01:33:44 +08:00
parent 80d46dcd5d
commit f0b35e6b34
18 changed files with 933 additions and 300 deletions
+91
View File
@@ -95,6 +95,97 @@ class CoreFlowTest(unittest.TestCase):
denied = self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-A"})
self.assertEqual(denied.status_code, 403, denied.text)
def test_banned_node_rejects_heartbeat(self) -> None:
self._register("0xban", "NODE-BAN", "100")
online = self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-BAN"})
self.assertEqual(online.status_code, 200, online.text)
banned = self.client.post(
"/api/v1/admin/nodes/NODE-BAN/ban",
headers=self._admin_headers(),
json={"is_banned": True},
)
self.assertEqual(banned.status_code, 200, banned.text)
self.assertTrue(banned.json()["is_banned"])
self.assertEqual(banned.json()["status"], "offline")
denied = self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-BAN"})
self.assertEqual(denied.status_code, 403, denied.text)
self.assertEqual(denied.json()["detail"], "节点已封禁")
released = self.client.post(
"/api/v1/admin/nodes/NODE-BAN/ban",
headers=self._admin_headers(),
json={"is_banned": False},
)
self.assertEqual(released.status_code, 200, released.text)
self.assertFalse(released.json()["is_banned"])
again = self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-BAN"})
self.assertEqual(again.status_code, 200, again.text)
def test_admin_can_dispatch_and_collect_task_results(self) -> None:
self._register("0xtask1", "NODE-TASK-1", "100")
self._register("0xtask2", "NODE-TASK-2", "100")
self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-TASK-1"})
self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-TASK-2"})
headers = self._admin_headers()
missing = self.client.post(
"/api/v1/admin/tasks/create",
headers=headers,
json={"task_type": "not_a_task", "payload": {}},
)
self.assertEqual(missing.status_code, 400, missing.text)
broadcast = self.client.post(
"/api/v1/admin/tasks/create",
headers=headers,
json={"task_type": "alpha_mining", "payload": {"operators": ["rank"], "population": 32}},
)
self.assertEqual(broadcast.status_code, 200, broadcast.text)
self.assertEqual(len(broadcast.json()), 2)
self.assertEqual({row["status"] for row in broadcast.json()}, {"pending"})
targeted = self.client.post(
"/api/v1/admin/tasks/create",
headers=headers,
json={
"task_type": "lora_training",
"target_node_sn": "NODE-TASK-1",
"payload": {"base_model": "qwen2.5-7b", "rank": 8},
},
)
self.assertEqual(targeted.status_code, 200, targeted.text)
task = targeted.json()[0]
self.assertEqual(task["target_node_sn"], "NODE-TASK-1")
claimed = self.client.get("/api/v1/node/NODE-TASK-1/tasks")
self.assertEqual(claimed.status_code, 200, claimed.text)
handed = next(row for row in claimed.json() if row["task_id"] == task["task_id"])
self.assertEqual(handed["status"], "processing")
reported = self.client.post(
"/api/v1/node/task/report",
json={
"sn": "NODE-TASK-1",
"task_id": task["task_id"],
"success": True,
"nonce": handed["payload"]["nonce"],
"result": {"formula": "rank(ts_mean(close, 12))", "patch_bytes": 4096},
},
)
self.assertEqual(reported.status_code, 200, reported.text)
listed = self.client.get("/api/v1/admin/tasks/list", headers=headers)
self.assertEqual(listed.status_code, 200, listed.text)
done = next(row for row in listed.json() if row["task_id"] == task["task_id"])
self.assertEqual(done["status"], "completed")
self.assertEqual(done["result_preview"], "rank(ts_mean(close, 12))")
self._register("0xtask3", "NODE-TASK-3", "100")
self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-TASK-3"})
self.client.post(
"/api/v1/admin/nodes/NODE-TASK-3/ban",
headers=headers,
json={"is_banned": True},
)
blocked = self.client.post(
"/api/v1/admin/tasks/create",
headers=headers,
json={"task_type": "stress_test", "target_node_sn": "NODE-TASK-3", "payload": {"matrix": 128}},
)
self.assertEqual(blocked.status_code, 409, blocked.text)
def test_task_report_requires_nonce(self) -> None:
self._register("0xdef", "NODE-T", "50")
self.client.post("/api/v1/node/heartbeat", json={"sn": "NODE-T"})