feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,102 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"evobgp/internal/birdfmt"
|
||||
"evobgp/internal/store"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTelemetryHydrationFreshnessAndConfiguration(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
sp := &store.Speaker{ID: "replica", Role: "replica", Endpoint: "old"}
|
||||
observation := store.TelemetryObservation{ObservedAt: now.Add(-time.Hour), ReceivedAt: now, LastSuccessAt: now, Source: "agent"}
|
||||
base := store.TelemetrySnapshot{SpeakerID: sp.ID, Kind: "remote_agent", ConfigFingerprint: store.TelemetrySpeakerFingerprint(sp), BGP: store.TelemetryBGP{TelemetryObservation: observation, Sessions: []birdfmt.BGPSession{{Name: "peer", State: "Established"}}}, Health: store.TelemetryHealth{TelemetryObservation: observation, OK: true}}
|
||||
cases := []struct {
|
||||
name string
|
||||
mutate func(*store.TelemetrySnapshot)
|
||||
bgp, health bool
|
||||
quality string
|
||||
}{
|
||||
{"fresh receipt despite remote clock", func(*store.TelemetrySnapshot) {}, true, true, "ok"},
|
||||
{"expired BGP independent of health", func(s *store.TelemetrySnapshot) { s.BGP.ReceivedAt = now.Add(-2 * time.Minute) }, false, true, "stale"},
|
||||
{"expired health independent of BGP", func(s *store.TelemetrySnapshot) { s.Health.ReceivedAt = now.Add(-2 * time.Minute) }, true, false, "ok"},
|
||||
{"future receipt is not fresh", func(s *store.TelemetrySnapshot) { s.BGP.ReceivedAt = now.Add(time.Hour) }, false, true, "stale"},
|
||||
{"configuration changed", func(s *store.TelemetrySnapshot) { s.ConfigFingerprint = "old" }, false, false, "unknown"},
|
||||
{"failed BGP", func(s *store.TelemetrySnapshot) { s.BGP.Error = "timeout" }, false, true, "stale"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
snapshot := base
|
||||
tc.mutate(&snapshot)
|
||||
views, statuses := hydrateTelemetry([]*store.Speaker{sp}, []store.TelemetrySnapshot{snapshot}, now, time.Minute)
|
||||
status := statuses[sp.ID]
|
||||
if status["bgp_poll_ok"] != tc.bgp || status["agent_ok"] != tc.health || views[0].observation().Quality != tc.quality {
|
||||
t.Fatalf("status=%+v observation=%+v", status, views[0].observation())
|
||||
}
|
||||
if !tc.bgp && (status["bgp_established"] != nil || len(views[0].Sessions) != 0) {
|
||||
t.Fatal("stale BGP exposed live sessions")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetryHydrationMissingAndAmbiguousLocal(t *testing.T) {
|
||||
now := time.Now()
|
||||
sp := &store.Speaker{ID: "master", Role: "master"}
|
||||
for _, rows := range [][]store.TelemetrySnapshot{nil, {{SpeakerID: sp.ID, Kind: "local_bird", LocalAuthority: "a"}, {SpeakerID: sp.ID, Kind: "local_bird", LocalAuthority: "b"}}} {
|
||||
views, statuses := hydrateTelemetry([]*store.Speaker{sp}, rows, now, time.Minute)
|
||||
if views[0].observation().Quality != "unknown" || statuses[sp.ID]["agent_ok"] == true || statuses[sp.ID]["bgp_poll_ok"] == true {
|
||||
t.Fatalf("unexpected healthy master: %+v", statuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetryHydrationLocalAuthorityRestart(t *testing.T) {
|
||||
now := time.Now()
|
||||
sp := &store.Speaker{ID: "master", Role: "master"}
|
||||
fresh := store.TelemetrySnapshot{SpeakerID: sp.ID, Kind: "local_bird", LocalAuthority: "new", ConfigFingerprint: store.TelemetrySpeakerFingerprint(sp), BGP: store.TelemetryBGP{TelemetryObservation: store.TelemetryObservation{ReceivedAt: now}}}
|
||||
old := fresh
|
||||
old.LocalAuthority = "old"
|
||||
old.BGP.ReceivedAt = now.Add(-2 * time.Minute)
|
||||
_, status := hydrateTelemetry([]*store.Speaker{sp}, []store.TelemetrySnapshot{old, fresh}, now, time.Minute)
|
||||
if status[sp.ID]["bgp_poll_ok"] != true {
|
||||
t.Fatal("expired authority hides current local snapshot")
|
||||
}
|
||||
old.BGP.ReceivedAt = now
|
||||
_, status = hydrateTelemetry([]*store.Speaker{sp}, []store.TelemetrySnapshot{old, fresh}, now, time.Minute)
|
||||
if status[sp.ID]["bgp_poll_ok"] != false {
|
||||
t.Fatal("two current local authorities are ambiguous")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetryPeerObservationPreservesUnknownConfiguration(t *testing.T) {
|
||||
now := time.Now()
|
||||
sp := &store.Speaker{ID: "replica", Role: "replica"}
|
||||
row := store.TelemetrySnapshot{SpeakerID: sp.ID, Kind: "remote_agent", ConfigFingerprint: "previous", BGP: store.TelemetryBGP{TelemetryObservation: store.TelemetryObservation{ReceivedAt: now, LastSuccessAt: now}}}
|
||||
views, _ := hydrateTelemetry([]*store.Speaker{sp}, []store.TelemetrySnapshot{row}, now, time.Minute)
|
||||
observation := peerLiveObservation(views)
|
||||
if observation.Quality != "unknown" || observation.ErrorCode != "configuration_changed" {
|
||||
t.Fatalf("changed config is not a failed current poll: %+v", observation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetryExpiryUsesConfiguredIntervalAndEarliestSource(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
old := now.Add(-30 * time.Second)
|
||||
a := snapshotObservation(store.TelemetryObservation{ReceivedAt: old}, now, 5*time.Minute, "")
|
||||
b := snapshotObservation(store.TelemetryObservation{ReceivedAt: now}, now, 5*time.Minute, "")
|
||||
want := old.Add(5 * time.Minute).Format(time.RFC3339Nano)
|
||||
if a.ExpiresAt == nil || *a.ExpiresAt != want {
|
||||
t.Fatalf("expiry ignores configured interval: %+v", a)
|
||||
}
|
||||
views := []speakerBGPLive{{Polled: true, ObservationOverride: &b}, {Polled: true, ObservationOverride: &a}}
|
||||
combined := peerLiveObservation(views)
|
||||
if combined.ExpiresAt == nil || *combined.ExpiresAt != want {
|
||||
t.Fatalf("aggregate extends oldest source: %+v", combined)
|
||||
}
|
||||
missing := snapshotObservation(store.TelemetryObservation{}, now, time.Minute, "")
|
||||
if missing.ExpiresAt != nil {
|
||||
t.Fatal("unobserved source has expiry")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user