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,153 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestSameSnapshotContentIgnoresOrder(t *testing.T) {
|
||||
comm := "c1"
|
||||
a := &ModulePrefixSnapshot{InputHash: "h", Prefixes: []PrefixRow{
|
||||
{Prefix: "203.0.113.0/24", CommunityID: &comm, Source: "cdn:a"},
|
||||
{Prefix: "198.51.100.0/24", Source: "cdn:b"},
|
||||
}}
|
||||
b := &ModulePrefixSnapshot{InputHash: "h", Prefixes: []PrefixRow{
|
||||
{Prefix: "198.51.100.0/24", Source: "cdn:b"},
|
||||
{Prefix: "203.0.113.0/24", CommunityID: &comm, Source: "cdn:a"},
|
||||
}}
|
||||
if !SameSnapshotContent(a, b) {
|
||||
t.Fatal("same set in another order was treated as new")
|
||||
}
|
||||
b.Prefixes[0].Prefix = "192.0.2.0/24"
|
||||
if SameSnapshotContent(a, b) {
|
||||
t.Fatal("different prefix compared equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixSnapshotHashIgnoresOrder(t *testing.T) {
|
||||
rows := []PrefixRow{
|
||||
{Prefix: "203.0.113.0/24", Source: "ip_range"},
|
||||
{Prefix: "198.51.100.0/24", Source: "cdn:a"},
|
||||
}
|
||||
rev := []PrefixRow{rows[1], rows[0]}
|
||||
if PrefixSnapshotHash(rows) != PrefixSnapshotHash(rev) {
|
||||
t.Fatal("prefix hash depends on order")
|
||||
}
|
||||
rev = append(rev, PrefixRow{Prefix: "2001:db8::/32", Source: "ip_range"})
|
||||
if PrefixSnapshotHash(rows) == PrefixSnapshotHash(rev) {
|
||||
t.Fatal("prefix change kept the same hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRenderRevisionReusesPrefixSnapshot(t *testing.T) {
|
||||
m := NewMemory()
|
||||
m.SeedDemo()
|
||||
tenant, _, mod, _, _ := m.DemoIDs()
|
||||
ctx := context.Background()
|
||||
rows := []PrefixRow{
|
||||
{Prefix: "203.0.113.0/24", Source: "ip_range"},
|
||||
{Prefix: "198.51.100.0/24", Source: "cdn:a"},
|
||||
}
|
||||
rev1 := uuid.NewString()
|
||||
if err := m.CreateRenderRevision(ctx, rev1, tenant, mod, nil, "hash-peer-a", map[string]string{"bird.conf": "a"}, rows); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rev2 := uuid.NewString()
|
||||
reversed := []PrefixRow{rows[1], rows[0]}
|
||||
if err := m.CreateRenderRevision(ctx, rev2, tenant, mod, nil, "hash-peer-b", map[string]string{"bird.conf": "b"}, reversed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.revPrefixSnap[rev1] == "" || m.revPrefixSnap[rev1] != m.revPrefixSnap[rev2] {
|
||||
t.Fatalf("peer-only change copied prefix rows: %q vs %q", m.revPrefixSnap[rev1], m.revPrefixSnap[rev2])
|
||||
}
|
||||
if len(m.prefixSnapRows) != 1 {
|
||||
t.Fatalf("stored prefix snapshots: %d", len(m.prefixSnapRows))
|
||||
}
|
||||
page, _, _ := m.ListRevisionPrefixes(tenant, rev2, "", 10)
|
||||
if len(page) != 2 {
|
||||
t.Fatalf("reused snapshot not readable: %+v", page)
|
||||
}
|
||||
changed := append(append([]PrefixRow(nil), rows...), PrefixRow{Prefix: "2001:db8::/32", Source: "ip_range"})
|
||||
rev3 := uuid.NewString()
|
||||
if err := m.CreateRenderRevision(ctx, rev3, tenant, mod, nil, "hash-prefix", nil, changed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.revPrefixSnap[rev3] == m.revPrefixSnap[rev1] || len(m.prefixSnapRows) != 2 {
|
||||
t.Fatalf("prefix change did not allocate a snapshot: id=%s count=%d", m.revPrefixSnap[rev3], len(m.prefixSnapRows))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateModulePrefixSnapshotCancelledContext(t *testing.T) {
|
||||
m := NewMemory()
|
||||
m.SeedDemo()
|
||||
tenant, mod, _, _, _ := m.DemoIDs()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
err := m.UpdateModulePrefixSnapshot(ctx, tenant, mod, func(*ModulePrefixSnapshot) (*ModulePrefixSnapshot, error) {
|
||||
t.Fatal("snapshot update ran after cancel")
|
||||
return nil, nil
|
||||
})
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemorySnapshotLockUsesSnapshotTimeout(t *testing.T) {
|
||||
t.Setenv("EVOBGP_SNAPSHOT_TIMEOUT", "80ms")
|
||||
m := NewMemory()
|
||||
hold, err := m.lockModuleSnapshot(context.Background(), "tenant", "module")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer hold()
|
||||
start := time.Now()
|
||||
_, err = m.lockModuleSnapshot(context.Background(), "tenant", "module")
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
if time.Since(start) > time.Second {
|
||||
t.Fatalf("lock wait ignored snapshot timeout: %s", time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotWriteBudgetKeepsCallerDeadline(t *testing.T) {
|
||||
parent, cancel := context.WithTimeout(context.Background(), time.Hour)
|
||||
defer cancel()
|
||||
got, stop, installed := SnapshotWriteBudget(parent)
|
||||
defer stop()
|
||||
if installed {
|
||||
t.Fatal("caller deadline was replaced")
|
||||
}
|
||||
dl, ok := got.Deadline()
|
||||
if !ok || time.Until(dl) < 30*time.Minute {
|
||||
t.Fatal("caller deadline was shortened")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetASNPrefixCacheKeepsRowsWhenSetUnchanged(t *testing.T) {
|
||||
m := NewMemory()
|
||||
if err := m.SetASNPrefixCache(64500, "holder-a", []string{"203.0.113.0/24", "198.51.100.0/24"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := m.asnPrefixCache[64500]
|
||||
ptr := &before.Prefixes[0]
|
||||
fetched := before.FetchedAt
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
if err := m.SetASNPrefixCache(64500, "holder-b", []string{"198.51.100.0/24", "203.0.113.0/24"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after := m.asnPrefixCache[64500]
|
||||
if &after.Prefixes[0] != ptr {
|
||||
t.Fatal("unchanged ASN prefixes were rewritten")
|
||||
}
|
||||
if !after.FetchedAt.After(fetched) {
|
||||
t.Fatal("fetched_at was not updated")
|
||||
}
|
||||
if after.Holder != "holder-b" {
|
||||
t.Fatalf("holder=%q", after.Holder)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user