Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/birdfmt"
|
|
"evobgp/internal/nodedispatch"
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
type agentHealthPoll struct {
|
|
result nodedispatch.AgentHealthResult
|
|
at time.Time
|
|
lastSuccessAt time.Time
|
|
observationOverride *liveObservation
|
|
}
|
|
|
|
func agentHealthObservation(poll *agentHealthPoll) liveObservation {
|
|
if poll == nil {
|
|
return liveObservation{Source: "none", Quality: "unknown", ErrorCode: "not_polled"}
|
|
}
|
|
if poll.observationOverride != nil {
|
|
return *poll.observationOverride
|
|
}
|
|
o := liveObservation{ObservedAt: liveTime(poll.at), ReceivedAt: liveTime(poll.at), LastSuccessAt: liveTime(poll.lastSuccessAt), Source: "agent_health", Quality: "ok"}
|
|
if !poll.result.OK {
|
|
o.Quality = "unreachable"
|
|
if !poll.lastSuccessAt.IsZero() {
|
|
o.Quality = "stale"
|
|
}
|
|
o.ErrorCode = liveErrorCode(poll.result.Error)
|
|
if poll.result.Error == "agent reported ok=false" {
|
|
o.ErrorCode = "reported_unhealthy"
|
|
}
|
|
}
|
|
return o
|
|
}
|
|
|
|
func countBGPSessions(sessions []birdfmt.BGPSession) (total, established int) {
|
|
total = len(sessions)
|
|
for _, s := range sessions {
|
|
if strings.EqualFold(strings.TrimSpace(s.State), "Established") {
|
|
established++
|
|
}
|
|
}
|
|
return total, established
|
|
}
|
|
|
|
func speakerLiveStatusJSON(sp *store.Speaker, view speakerBGPLive, health *nodedispatch.AgentHealthResult) map[string]any {
|
|
var poll *agentHealthPoll
|
|
if health != nil {
|
|
poll = &agentHealthPoll{result: *health}
|
|
}
|
|
return speakerLiveStatusWithPoll(sp, view, poll)
|
|
}
|
|
|
|
func speakerLiveStatusWithPoll(sp *store.Speaker, view speakerBGPLive, health *agentHealthPoll) map[string]any {
|
|
total, established := countBGPSessions(view.Sessions)
|
|
bgpObservation := view.observation()
|
|
agentObservation := agentHealthObservation(health)
|
|
if sp != nil && strings.EqualFold(strings.TrimSpace(sp.Role), "master") && health == nil {
|
|
agentObservation = bgpObservation
|
|
}
|
|
m := map[string]any{
|
|
"label": view.Label,
|
|
"bgp_poll_ok": view.successful(),
|
|
"bgp_sessions_total": nil,
|
|
"bgp_established": nil,
|
|
"bgp_observation": bgpObservation,
|
|
"agent_observation": agentObservation,
|
|
}
|
|
if view.successful() {
|
|
m["bgp_sessions_total"] = total
|
|
m["bgp_established"] = established
|
|
}
|
|
if view.Error != "" {
|
|
m["bgp_poll_error"] = view.Error
|
|
}
|
|
if health != nil {
|
|
m["agent_ok"] = health.result.OK
|
|
if health.result.Error != "" {
|
|
m["agent_error"] = health.result.Error
|
|
}
|
|
if health.result.LastSyncAt != "" {
|
|
m["agent_last_sync_at"] = health.result.LastSyncAt
|
|
}
|
|
if health.result.LastAppliedRevisionID != "" {
|
|
m["agent_last_applied_revision_id"] = health.result.LastAppliedRevisionID
|
|
}
|
|
} else if sp != nil && strings.EqualFold(strings.TrimSpace(sp.Role), "master") {
|
|
m["agent_ok"] = view.successful()
|
|
if view.Error != "" {
|
|
m["agent_error"] = view.Error
|
|
}
|
|
} else if sp != nil && store.SpeakerNeedsRemoteDispatch(sp.Role, store.ParseSpeakerMeta(sp.MetaJSON)) {
|
|
m["agent_ok"] = false
|
|
m["agent_error"] = "agent health not polled"
|
|
}
|
|
if view.successful() {
|
|
sess := make([]map[string]any, 0, len(view.Sessions))
|
|
for _, s := range view.Sessions {
|
|
row := map[string]any{
|
|
"name": s.Name,
|
|
"state": s.State,
|
|
}
|
|
if strings.TrimSpace(s.Neighbor) != "" {
|
|
row["neighbor"] = s.Neighbor
|
|
}
|
|
sess = append(sess, row)
|
|
}
|
|
m["sessions"] = sess
|
|
}
|
|
return m
|
|
}
|