完善部分平台治理功能,及修改看板缺陷

This commit is contained in:
wangjiming
2026-08-10 11:41:16 +08:00
parent 75cc105ebc
commit f809825a7d
18 changed files with 1873 additions and 362 deletions

View File

@@ -525,7 +525,7 @@ class PlatformStore:
},
)
schema_dir = Path(__file__).with_name("sql")
for extra in ("002_governance.sql", "003_tenant_quota.sql"):
for extra in ("002_governance.sql", "003_tenant_quota.sql", "004_permissions.sql"):
extra_path = schema_dir / extra
if extra_path.exists():
conn.executescript(extra_path.read_text(encoding="utf-8"))
@@ -667,8 +667,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO trained_models
(id, name, train_methods, base_model_path, create_time, merged, merging, merged_path, artifact_dir, compute_node_id, compute_node_name)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, train_methods, base_model_path, create_time, merged, merging, merged_path, artifact_dir, compute_node_id, compute_node_name, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
trained_model_id,
@@ -682,6 +682,7 @@ class PlatformStore:
output_dir,
task.get("compute_node_id"),
task.get("compute_node_code") or task.get("compute_node_name"),
task.get("created_by"),
),
)
# Use real artifact data from compute node when available
@@ -1284,10 +1285,22 @@ class PlatformStore:
row = conn.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
if not row:
raise KeyError(user_id)
is_admin = row["role"] == "admin" or bool(row["protected"])
if "permissions" in payload:
perms = payload["permissions"]
if is_admin:
# 管理员权限不可更改,必须是全部
perms = ALL_PERMISSIONS
else:
# 非 admin 用户不能拥有 user-settings 权限
perms = [p for p in (perms or []) if p != "user-settings"]
payload = {**payload, "permissions": perms}
values = {
"role": payload.get("role", row["role"]),
"status": payload.get("status", row["status"]),
"permissions": json_dumps(payload.get("permissions", json_loads(row["permissions"], []))),
"permissions": json_dumps(
payload.get("permissions", json_loads(row["permissions"], []))
),
}
conn.execute(
"UPDATE users SET role=?, status=?, permissions=? WHERE id=?",
@@ -1302,6 +1315,34 @@ class PlatformStore:
raise KeyError(user_id)
if row["protected"]:
raise ValueError("protected user cannot be deleted")
# 级联删除该用户关联的数据
tables_to_clean = [
# ACL 授权
("acls", "principal_type='user' AND principal_id=?", [user_id]),
# 审批实例(申请人)
("approval_instances", "applicant_id=?", [user_id]),
# 审计日志
("audit_logs", "actor_id=?", [user_id]),
# 项目成员
("project_members", "user_id=?", [user_id]),
# GPU 分配
("gpu_assignments", "user_id=?", [user_id]),
# 数据集
("datasets", "created_by=?", [user_id]),
# 基座模型
("models", "created_by=?", [user_id]),
# 微调产物
("trained_models", "created_by=?", [user_id]),
# 评测任务
("eval_tasks", "created_by=?", [user_id]),
# 对比/推理任务payload 中 creator
# 训练任务:仅标记为已删除或保留(有 compute_job_id 关联),不清物理数据
]
for table_name, where_clause, params in tables_to_clean:
try:
conn.execute(f"DELETE FROM {table_name} WHERE {where_clause}", params)
except Exception:
pass # 表可能不存在或字段不存在,跳过
conn.execute("DELETE FROM users WHERE id=?", (user_id,))
def reset_password(self, user_id: str, new_password: str) -> None:
@@ -1357,8 +1398,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO models
(id, name, type, purpose, model_source, description, path, api_url, api_key, online_model_name, can_train, create_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, type, purpose, model_source, description, path, api_url, api_key, online_model_name, can_train, create_time, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
model_id,
@@ -1373,6 +1414,7 @@ class PlatformStore:
payload.get("online_model_name"),
can_train,
utcnow(),
payload.get("created_by"),
),
)
return dict(conn.execute("SELECT * FROM models WHERE id=?", (model_id,)).fetchone())
@@ -1540,8 +1582,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO datasets
(id, name, type, storage_type, source, task_id, size, count, description, create_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, type, storage_type, source, task_id, size, count, description, create_time, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
dataset_id,
@@ -1554,6 +1596,7 @@ class PlatformStore:
payload.get("count", 0),
payload.get("description"),
utcnow(),
payload.get("created_by"),
),
)
return self._dataset(conn, conn.execute("SELECT * FROM datasets WHERE id=?", (dataset_id,)).fetchone())
@@ -4074,6 +4117,130 @@ class PlatformStore:
"DELETE FROM retention_policies WHERE id=?", (policy_id,)
)
# ===================== 平台治理GPU 算力分配 =====================
def gpu_assignments(self) -> list[dict[str, Any]]:
"""查询全部分配关系。"""
with self.connect() as conn:
rows = conn.execute(
"""
SELECT ga.*, u.username, u.display_name,
n.code AS node_code, n.name AS node_name, g.name AS gpu_name
FROM gpu_assignments ga
LEFT JOIN users u ON u.id = ga.user_id
LEFT JOIN compute_nodes n ON n.id = ga.node_id
LEFT JOIN gpus g ON g.node_id = ga.node_id AND g.gpu_index = ga.gpu_index
ORDER BY ga.assigned_at DESC
"""
).fetchall()
return [dict(r) for r in rows]
def gpu_assignments_for_user(self, user_id: str) -> list[dict[str, Any]]:
"""查询某用户被分配的 GPU 列表。"""
with self.connect() as conn:
rows = conn.execute(
"""
SELECT ga.node_id, ga.gpu_index,
n.code AS node_code, n.name AS node_name,
g.name AS gpu_name, g.uuid, g.memory_total_gb
FROM gpu_assignments ga
JOIN compute_nodes n ON n.id = ga.node_id
LEFT JOIN gpus g ON g.node_id = ga.node_id AND g.gpu_index = ga.gpu_index
WHERE ga.user_id = ?
ORDER BY n.code, ga.gpu_index
""",
(user_id,),
).fetchall()
return [dict(r) for r in rows]
def assign_gpus(self, assignments: list[dict[str, Any]], assigned_by: str | None = None) -> list[dict[str, Any]]:
"""批量分配 GPU幂等已存在的分配跳过"""
now = utcnow()
with self.connect() as conn:
for a in assignments:
node_id = a["node_id"]
gpu_index = a["gpu_index"]
user_id = a["user_id"]
existing = conn.execute(
"SELECT id FROM gpu_assignments WHERE node_id=? AND gpu_index=? AND user_id=?",
(node_id, gpu_index, user_id),
).fetchone()
if existing:
continue
aid = new_id("ga")
conn.execute(
"""
INSERT INTO gpu_assignments (id, node_id, gpu_index, user_id, assigned_by, assigned_at)
VALUES (?, ?, ?, ?, ?, ?)
""",
(aid, node_id, gpu_index, user_id, assigned_by, now),
)
return self.gpu_assignments()
def unassign_gpu(self, assignment_id: str) -> None:
with self.connect() as conn:
conn.execute("DELETE FROM gpu_assignments WHERE id=?", (assignment_id,))
def check_gpu_access(self, user_id: str, node_id: str, gpu_indices: list[int]) -> bool:
"""检查用户是否被分配了指定节点的指定 GPU 卡。"""
if not gpu_indices:
return True
with self.connect() as conn:
rows = conn.execute(
"""
SELECT gpu_index FROM gpu_assignments
WHERE user_id=? AND node_id=?
""",
(user_id, node_id),
).fetchall()
assigned = {r["gpu_index"] for r in rows}
return all(idx in assigned for idx in gpu_indices)
# ===================== 平台治理:资源可见性过滤 =====================
def _filter_accessible_ids(
self, resource_type: str, all_ids: list[str], user: dict[str, Any]
) -> list[str]:
"""从全部资源 ID 中过滤出当前用户可访问的 ID 列表。
- admin 直接返回全部。
- 资源所有者可见(需调用方在 all_ids 中提供 owned ids
- ACL 授权的用户/角色可见。
"""
if user.get("role") == "admin" or user.get("protected"):
return all_ids
if not all_ids:
return []
user_id = user.get("id")
user_role = user.get("role")
with self.connect() as conn:
rows = conn.execute(
"""
SELECT DISTINCT resource_id FROM acls
WHERE resource_type=? AND (
(principal_type='user' AND principal_id=?)
OR (principal_type='role' AND principal_id=?)
)
""",
(resource_type, user_id, user_role),
).fetchall()
accessible = {r["resource_id"] for r in rows}
return [rid for rid in all_ids if rid in accessible]
def change_password(self, user_id: str, old_password: str, new_password: str) -> bool:
"""用户自行修改密码:验证旧密码后设置新密码。"""
with self.connect() as conn:
row = conn.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
if not row:
raise KeyError(user_id)
matched, _ = verify_password(old_password, row["password_hash"])
if not matched:
return False
conn.execute(
"UPDATE users SET password_hash=? WHERE id=?",
(hash_password(new_password), user_id),
)
return True
_store: PlatformStore | None = None