按显卡世代和创世编号结算回本,机房批量接入需审核网段并用硬件指纹防克隆。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,9 @@ from app.config import settings
|
||||
from app.errors import AppError
|
||||
from app.models import ComputeNode, NodeStatus, UptimeInterval
|
||||
from app.services.common import get_node_by_sn
|
||||
from app.services.datacenter import assert_fingerprint, assert_subnet_gpu
|
||||
from app.services.ip_guard import assert_ip_available
|
||||
from app.services.hardware import GENESIS_RECOVERY_DAYS, HardwareReport, classify_generation, is_genesis, score_hardware
|
||||
|
||||
|
||||
def assert_node_token(node: ComputeNode, token: str | None) -> None:
|
||||
@@ -24,14 +27,33 @@ def assert_node_token(node: ComputeNode, token: str | None) -> None:
|
||||
raise AppError("节点 Token 不正确", 403)
|
||||
|
||||
|
||||
def record_heartbeat(session: Session, sn: str, token: str | None = None, now: datetime | None = None) -> ComputeNode:
|
||||
def record_heartbeat(
|
||||
session: Session,
|
||||
sn: str,
|
||||
token: str | None = None,
|
||||
now: datetime | None = None,
|
||||
hardware: HardwareReport | None = None,
|
||||
client_ip: str | None = None,
|
||||
hardware_fingerprint: str | None = None,
|
||||
) -> ComputeNode:
|
||||
"""刷新最后活跃时间。离线节点收到心跳后重新打开一段在线区间。"""
|
||||
moment = now or utcnow()
|
||||
node = get_node_by_sn(session, sn.strip())
|
||||
assert_node_token(node, token)
|
||||
if node.license_expires_at <= moment:
|
||||
raise AppError("节点授权已到期", 403)
|
||||
bound_ip = (client_ip or "").strip() or None
|
||||
if bound_ip:
|
||||
assert_ip_available(session, bound_ip, node=node, now=moment)
|
||||
reported_gpu = hardware.gpu_model if hardware is not None and hardware.gpu_model else node.gpu_model
|
||||
assert_subnet_gpu(session, bound_ip, reported_gpu)
|
||||
node.client_ip = bound_ip
|
||||
fingerprint = assert_fingerprint(session, hardware_fingerprint, node.id)
|
||||
if fingerprint:
|
||||
node.hardware_fingerprint = fingerprint
|
||||
|
||||
if hardware is not None:
|
||||
_apply_hardware(node, hardware)
|
||||
node.last_heartbeat_at = moment
|
||||
node.updated_at = moment
|
||||
if node.status != NodeStatus.ONLINE:
|
||||
@@ -66,6 +88,26 @@ def mark_stale_nodes(session: Session, now: datetime | None = None) -> list[str]
|
||||
return changed
|
||||
|
||||
|
||||
def _apply_hardware(node: ComputeNode, hardware: HardwareReport) -> None:
|
||||
node.gpu_model = (hardware.gpu_model or "").strip()[:128] or None
|
||||
node.vram_gb = hardware.vram_gb
|
||||
node.cpu_count = hardware.cpu_count
|
||||
node.memory_gb = hardware.memory_gb
|
||||
node.hardware_weight = float(score_hardware(node.gpu_model, node.vram_gb))
|
||||
if node.generation_locked or not node.gpu_model:
|
||||
return
|
||||
generation, recovery_days = classify_generation(node.gpu_model)
|
||||
node.gpu_generation = generation
|
||||
node.generation_locked = True
|
||||
if is_genesis(node.node_index):
|
||||
node.recovery_days = GENESIS_RECOVERY_DAYS
|
||||
return
|
||||
node.recovery_days = recovery_days
|
||||
horizon = node.licensed_at + timedelta(days=recovery_days)
|
||||
if horizon > node.license_expires_at:
|
||||
node.license_expires_at = horizon
|
||||
|
||||
|
||||
def _ensure_open_interval(session: Session, node_id: int, started_at: datetime) -> None:
|
||||
opened = session.scalar(
|
||||
select(UptimeInterval).where(
|
||||
|
||||
Reference in New Issue
Block a user