fix(nodes): 节点健康/connect 绑定 agent 在线状态,不再伪装连上
本次 google 打不开暴露:agent 离线 6 天,节点却一直显示健康、还让客户端 "连上"(凭证推送失败被 _ = 吞掉),实际节点不认 UUID。三处收口: - Hub.IsOnline(nodeUUID):基于本地 agent 流注册的存活信号(单实例权威; 多实例需 Redis presence,另议)。 - ListNodes:effectiveNodeStatus 把 DB='up' 但 agent 离线的节点降级为 "down",不再永远显示健康(status 列只反映供给/调度,不反映 agent 死活)。 - ConnectNode:agent 离线即 503 ErrNodeUnavailable(凭证持久化留待 resync), 在线时 push 失败也 503——不再吞 pushErr、不再下发节点无法兑现的配置。 测试:TestHub_IsOnline(注册/结束/未注册)、TestEffectiveNodeStatus (up+离线→down 等)。新增 apierr.ErrNodeUnavailable(503)。 掉线告警 + 静默吞错全量审计另起 TODO。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,21 @@ func (h *Hub) Register(nodeUUID string) (<-chan *agentv1.Command, func()) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnline reports whether an agent stream for nodeUUID is currently registered
|
||||
// on THIS instance — the authoritative agent-liveness signal for single-instance
|
||||
// deployments (the default). Used to refuse a connect / mark a node unhealthy
|
||||
// when its agent is offline, instead of handing out a credential nothing applies.
|
||||
//
|
||||
// Multi-instance note: an agent connected to a *different* instance is not in
|
||||
// this instance's subs map, so a correct multi-instance answer needs a
|
||||
// Redis-backed presence layer (deferred; prod runs single-instance).
|
||||
func (h *Hub) IsOnline(nodeUUID string) bool {
|
||||
h.mu.RLock()
|
||||
_, ok := h.subs[nodeUUID]
|
||||
h.mu.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Replay returns all unacked commands for nodeUUID with command_id > afterID, ordered ascending.
|
||||
// This is called during Subscribe to re-send commands the agent may have missed.
|
||||
func (h *Hub) Replay(ctx context.Context, nodeUUID string, afterID int64) ([]*agentv1.Command, error) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package nodes
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestHub_IsOnline verifies the agent-liveness signal that ListNodes/ConnectNode
|
||||
// now rely on: a node is online exactly while its agent stream is registered.
|
||||
func TestHub_IsOnline(t *testing.T) {
|
||||
h := NewHub(nil) // IsOnline/Register touch only the subs map, never Redis.
|
||||
const node = "node-1"
|
||||
|
||||
if h.IsOnline(node) {
|
||||
t.Fatal("IsOnline=true before any agent registered")
|
||||
}
|
||||
|
||||
_, done := h.Register(node)
|
||||
if !h.IsOnline(node) {
|
||||
t.Fatal("IsOnline=false while agent stream registered")
|
||||
}
|
||||
if h.IsOnline("other") {
|
||||
t.Fatal("IsOnline=true for a never-registered node")
|
||||
}
|
||||
|
||||
done() // agent stream ends
|
||||
if h.IsOnline(node) {
|
||||
t.Fatal("IsOnline=true after the stream ended")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user