Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
317 lines
8.1 KiB
Go
317 lines
8.1 KiB
Go
package lookup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func TestLookupIPEntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
|
|
cid := ""
|
|
comms, err := m.ListCommunities(tenant)
|
|
if err != nil || len(comms) == 0 {
|
|
t.Fatal("expected demo community")
|
|
}
|
|
cid = comms[0].ID
|
|
|
|
def := cid
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &def}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
entryComm := cid
|
|
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &entryComm,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := m.SetModulePrefixSnapshot(context.Background(), tenant, modIP, "hash1", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "203.0.113.10")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindIP || res.Normalized != "203.0.113.10" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched || res.MatchCount < 2 {
|
|
t.Fatalf("expected entry+snapshot matches, got %+v", res)
|
|
}
|
|
|
|
var entryHit, snapHit bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
if hit.Community != "demo-comm" || hit.CommunityTitle != "Demo" {
|
|
t.Fatalf("entry community: %+v", hit)
|
|
}
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
|
|
snapHit = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("missing layers entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
}
|
|
|
|
func TestLookupIPCommunityFallback(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{Prefix: "10.0.0.0/8"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "10.1.2.3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatal("expected match")
|
|
}
|
|
found := false
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry {
|
|
found = true
|
|
if hit.CommunityID == nil || *hit.CommunityID != cid {
|
|
t.Fatalf("expected default community, got %+v", hit)
|
|
}
|
|
if hit.Community != "demo-comm" {
|
|
t.Fatalf("community value: %+v", hit)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("no entry match")
|
|
}
|
|
}
|
|
|
|
func TestLookupDomainEntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
|
|
mod, err := m.CreateModule(tenant, &store.Module{
|
|
Type: "DOMAINS",
|
|
Name: "demo-domains",
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
|
|
e, err := m.CreateDomainEntry(tenant, mod.ID, &store.DomainEntry{
|
|
FQDN: "Example.COM.",
|
|
CommunityID: &cid,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(context.Background(), tenant, mod.ID, "hash-d", []store.PrefixRow{
|
|
{Prefix: "198.51.100.1/32", CommunityID: &cid, Source: "domain"},
|
|
{Prefix: "203.0.113.9/32", CommunityID: &cid, Source: "other"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
noDNS := func(context.Context, string) ([]netip.Addr, error) { return nil, nil }
|
|
res, err := LookupWithResolver(context.Background(), m, tenant, "example.com", noDNS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindDomain || res.Normalized != "example.com" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatal("expected match")
|
|
}
|
|
|
|
var entryHit, snapHit, otherSnap bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "198.51.100.1/32" {
|
|
snapHit = true
|
|
}
|
|
if hit.MatchedValue == "203.0.113.9/32" {
|
|
otherSnap = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
if otherSnap {
|
|
t.Fatal("non-domain snapshot source should be excluded")
|
|
}
|
|
}
|
|
|
|
func TestLookupDomainResolvedIPAgainstRanges(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &cid,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(context.Background(), tenant, modIP, "hash-r", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
fake := func(_ context.Context, host string) ([]netip.Addr, error) {
|
|
if host != "google.com" {
|
|
t.Fatalf("unexpected host %q", host)
|
|
}
|
|
return []netip.Addr{netip.MustParseAddr("203.0.113.50")}, nil
|
|
}
|
|
|
|
res, err := LookupWithResolver(context.Background(), m, tenant, "google.com", fake)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindDomain {
|
|
t.Fatalf("kind: %+v", res)
|
|
}
|
|
if len(res.ResolvedIPs) != 1 || res.ResolvedIPs[0] != "203.0.113.50" {
|
|
t.Fatalf("resolved_ips: %+v", res.ResolvedIPs)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatalf("expected IP membership via resolve, got %+v", res)
|
|
}
|
|
var viaResolve bool
|
|
for _, hit := range res.Matches {
|
|
if hit.ResolvedIP == "203.0.113.50" && hit.MatchedValue == "203.0.113.0/24" {
|
|
viaResolve = true
|
|
}
|
|
}
|
|
if !viaResolve {
|
|
t.Fatalf("missing resolved-ip match: %+v", res.Matches)
|
|
}
|
|
}
|
|
|
|
func TestLookupCIDREntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
|
|
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &cid,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(context.Background(), tenant, modIP, "hash-cidr", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "203.0.113.0/24")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindCIDR || res.Normalized != "203.0.113.0/24" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched || res.MatchCount < 2 {
|
|
t.Fatalf("expected entry+snapshot, got %+v", res)
|
|
}
|
|
|
|
var entryHit, snapHit bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
|
|
snapHit = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
|
|
// Narrower query covered by wider entry.
|
|
res2, err := Lookup(context.Background(), m, tenant, "203.0.113.128/25")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res2.QueryKind != KindCIDR || !res2.Matched {
|
|
t.Fatalf("expected cover match: %+v", res2)
|
|
}
|
|
}
|
|
|
|
func TestLookupNoMatch(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "192.0.2.1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Matched || res.MatchCount != 0 || len(res.Matches) != 0 {
|
|
t.Fatalf("expected empty: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestLookupInvalid(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
ctx := context.Background()
|
|
|
|
_, err := Lookup(ctx, m, tenant, "")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("empty: %v", err)
|
|
}
|
|
_, err = Lookup(ctx, m, tenant, "not a host")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("spaces: %v", err)
|
|
}
|
|
_, err = Lookup(ctx, m, tenant, "localhost")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("single label: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeFQDN(t *testing.T) {
|
|
got, ok := normalizeFQDN(" Example.COM. ")
|
|
if !ok || got != "example.com" {
|
|
t.Fatalf("got %q ok=%v", got, ok)
|
|
}
|
|
}
|