feat: 成本预算与失败通知第一批(G1/G2/G5/G6/无价上报)
- G1:修正 _check_quota docstring(Local popen 30 秒轮询掐断、 Docker/同步路径只有边界检查),README 配额节同步;followup 创建不查配额是已声明设计,保持不动。 - G2:notify_failure 对插值 shlex.quote,模板错误记日志不静默 吞,命令返回值/超时/异常记 host-run.log;dispatch 前置预算门 与 goal 预算中断补 budget-exceeded 通知(没配命令时只写日志)。 - G5/G6:重试与续跑的配额检查按任务创建者查(费用记创建者名下), 维护者替他人操作不再用错人的额度;续跑 API 侧补查,超限回 402 且不改状态,不再先 200 排队再悄悄 interrupted。 - 无价上报:配额自查/管理端点返回增加 unpriced_actual_tokens (actual 且 usd_micros 为 NULL,与 CostStore 口径一致),只读 上报、不计价、不拦任务;不给 gpt-5.6-terra 编造价格。 测试:host 68、cloud 760(4 跳过)、deploy 19 全过。
This commit is contained in:
1 parent
c2f82da5e0
commit
e4b5545a24
5 files changed
+304
-38
No files matched your search
@@ -234,6 +234,95 @@ class ServerQuotasTestCase(unittest.TestCase):
|
||||
self.assertEqual(s, 200, retried)
|
||||
self.assertEqual(retried["status"], "queued")
|
||||
|
||||
def test_retry_uses_creator_quota_not_operator(self):
|
||||
# 重试按任务创建者查配额:费用记创建者名下,操作人
|
||||
# 是谁不应影响准入。other 须是 maintainer 才能过
|
||||
# approve_merge 权限门,否则 403 会掩盖配额判断。
|
||||
self.auth.set_member(self.project_id, self.owner_id,
|
||||
self.other_id, "maintainer")
|
||||
s, task = self._create_task()
|
||||
self.assertEqual(s, 201, task)
|
||||
_insert_cost(self.db_path, task["id"], self.project_id,
|
||||
2000000, _utc_today())
|
||||
_set_task_status(self.db_path, task["id"], "failed")
|
||||
# 创建者超限:维护者替他重试也应拦。
|
||||
s, _ = self._set_quota(self.owner_id, 2000000)
|
||||
self.assertEqual(s, 200)
|
||||
s, body = _req(self.port, "POST",
|
||||
"/tasks/%s/retry" % task["id"], {},
|
||||
self.other_key)
|
||||
self.assertEqual(s, 402, body)
|
||||
# 创建者不限额、操作人额度为 0:按旧口径(查操作人)
|
||||
# 会拦,按创建者口径应放行。
|
||||
s, _ = self._set_quota(self.owner_id, None)
|
||||
self.assertEqual(s, 200)
|
||||
s, _ = self._set_quota(self.other_id, 0)
|
||||
self.assertEqual(s, 200)
|
||||
s, retried = _req(self.port, "POST",
|
||||
"/tasks/%s/retry" % task["id"], {},
|
||||
self.other_key)
|
||||
self.assertEqual(s, 200, retried)
|
||||
self.assertEqual(retried["status"], "queued")
|
||||
|
||||
def test_continue_blocked_at_creator_quota(self):
|
||||
s, task = self._create_task()
|
||||
self.assertEqual(s, 201, task)
|
||||
_insert_cost(self.db_path, task["id"], self.project_id,
|
||||
5000000, _utc_today())
|
||||
_set_task_status(self.db_path, task["id"], "completed")
|
||||
s, _ = self._set_quota(self.owner_id, 5000000)
|
||||
self.assertEqual(s, 200)
|
||||
# 以前:先回 200 排队、随后认领门悄悄标 interrupted。
|
||||
# 现在:API 侧直接 402,状态保持 completed 不变。
|
||||
s, body = _req(self.port, "POST",
|
||||
"/tasks/%s/continue" % task["id"],
|
||||
{"message": "again"}, self.owner_key)
|
||||
self.assertEqual(s, 402, body)
|
||||
s, row = _req(self.port, "GET", "/tasks/" + task["id"],
|
||||
key=self.owner_key)
|
||||
self.assertEqual(row["status"], "completed")
|
||||
# 额度调高后放行。
|
||||
s, _ = self._set_quota(self.owner_id, None)
|
||||
self.assertEqual(s, 200)
|
||||
s, continued = _req(self.port, "POST",
|
||||
"/tasks/%s/continue" % task["id"],
|
||||
{"message": "again"}, self.owner_key)
|
||||
self.assertEqual(s, 200, continued)
|
||||
self.assertEqual(continued["status"], "queued")
|
||||
# 续跑同样按创建者查:操作人额度为 0、创建者不限额时放行。
|
||||
self.auth.set_member(self.project_id, self.owner_id,
|
||||
self.other_id, "maintainer")
|
||||
s, task2 = self._create_task()
|
||||
self.assertEqual(s, 201, task2)
|
||||
_set_task_status(self.db_path, task2["id"], "completed")
|
||||
s, _ = self._set_quota(self.other_id, 0)
|
||||
self.assertEqual(s, 200)
|
||||
s, continued2 = _req(self.port, "POST",
|
||||
"/tasks/%s/continue" % task2["id"],
|
||||
{"message": "again"}, self.other_key)
|
||||
self.assertEqual(s, 200, continued2)
|
||||
self.assertEqual(continued2["status"], "queued")
|
||||
|
||||
def test_quota_reports_unpriced_tokens(self):
|
||||
s, task = self._create_task()
|
||||
self.assertEqual(s, 201, task)
|
||||
# 有价 5000000 计入 used;无价 actual 150 只进
|
||||
# unpriced_actual_tokens,不计价、不拦任务。
|
||||
_insert_cost(self.db_path, task["id"], self.project_id,
|
||||
5000000, _utc_today())
|
||||
_insert_cost(self.db_path, task["id"], self.project_id,
|
||||
None, _utc_today())
|
||||
s, q = _req(self.port, "GET", "/users/me/quota",
|
||||
key=self.owner_key)
|
||||
self.assertEqual(s, 200, q)
|
||||
self.assertEqual(q["used_usd_micros"], 5000000)
|
||||
self.assertEqual(q["unpriced_actual_tokens"], 150)
|
||||
s, q = _req(self.port, "GET",
|
||||
"/admin/quotas?user_id=" + self.owner_id,
|
||||
key=self.owner_key)
|
||||
self.assertEqual(s, 200, q)
|
||||
self.assertEqual(q["unpriced_actual_tokens"], 150)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user