check / check (push) Successful in 4m6s
The routing table records each prefix and origin AS pair's origin changes: when it gains its first peer and when it loses its last. Each row holds the prefix, the origin AS, announced or withdrawn, the peer count and the time. The prefix handler writes them to a new origin_changes table, and the database maintainer prunes each prefix to its newest 500 rows and 72 hours. The prefix page lists them newest first, at most 5,000. Model: deepseek-v4-pro
161 lines
4.4 KiB
Go
161 lines
4.4 KiB
Go
package database
|
|
|
|
import (
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/routetable"
|
|
)
|
|
|
|
// TestOriginChangesWriteThenRead writes three origin changes and reads them
|
|
// back newest first, honouring the limit.
|
|
func TestOriginChangesWriteThenRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := newRoutesTestDatabase(t)
|
|
prefix := netip.MustParsePrefix(tablePrefixV4)
|
|
|
|
changes := []routetable.OriginChange{
|
|
{Prefix: prefix, OriginASN: asA, Announced: true, PeerCount: 1,
|
|
Time: tableTime(10)},
|
|
{Prefix: prefix, OriginASN: asA, Announced: false, PeerCount: 0,
|
|
Time: tableTime(20)},
|
|
{Prefix: prefix, OriginASN: asB, Announced: true, PeerCount: 1,
|
|
Time: tableTime(30)},
|
|
}
|
|
|
|
err := db.WriteOriginChanges(changes)
|
|
if err != nil {
|
|
t.Fatalf("WriteOriginChanges: %v", err)
|
|
}
|
|
|
|
got, err := db.GetOriginChanges(t.Context(), prefix, 2)
|
|
if err != nil {
|
|
t.Fatalf("GetOriginChanges: %v", err)
|
|
}
|
|
|
|
want := []routetable.OriginChange{
|
|
{Prefix: prefix, OriginASN: asB, Announced: true, PeerCount: 1,
|
|
Time: tableTime(30)},
|
|
{Prefix: prefix, OriginASN: asA, Announced: false, PeerCount: 0,
|
|
Time: tableTime(20)},
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("GetOriginChanges = %+v, want %+v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesPrune checks that the prune keeps every row newer than 72
|
|
// hours, keeps the newest 500 rows of each prefix, deletes the rest, and
|
|
// leaves other prefixes alone.
|
|
func TestOriginChangesPrune(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := newRoutesTestDatabase(t)
|
|
oldPrefix := netip.MustParsePrefix(tablePrefixV4)
|
|
newPrefix := netip.MustParsePrefix(tablePrefixShortV4)
|
|
other := netip.MustParsePrefix("10.20.0.0/16")
|
|
|
|
now := time.Now()
|
|
|
|
changes := make([]routetable.OriginChange, 0, 1201)
|
|
|
|
// 600 rows for oldPrefix, all older than 72 hours, oldest first. The
|
|
// prune keeps the newest 500 and deletes the oldest 100.
|
|
for i := range 600 {
|
|
changes = append(changes, routetable.OriginChange{
|
|
Prefix: oldPrefix, OriginASN: asA, Announced: true, PeerCount: 1,
|
|
Time: now.Add(-100 * time.Hour).Add(time.Duration(i) * time.Minute),
|
|
})
|
|
}
|
|
|
|
// 600 rows for newPrefix, all newer than 72 hours, oldest first. The
|
|
// prune keeps all of them.
|
|
for i := range 600 {
|
|
changes = append(changes, routetable.OriginChange{
|
|
Prefix: newPrefix, OriginASN: asA, Announced: true, PeerCount: 1,
|
|
Time: now.Add(-71 * time.Hour).Add(time.Duration(i) * time.Minute),
|
|
})
|
|
}
|
|
|
|
// One row for other, which the prune leaves alone.
|
|
changes = append(changes, routetable.OriginChange{
|
|
Prefix: other, OriginASN: asA, Announced: true, PeerCount: 1,
|
|
Time: now.Add(-100 * time.Hour),
|
|
})
|
|
|
|
err := db.WriteOriginChanges(changes)
|
|
if err != nil {
|
|
t.Fatalf("WriteOriginChanges: %v", err)
|
|
}
|
|
|
|
err = db.PruneOriginChanges(t.Context())
|
|
if err != nil {
|
|
t.Fatalf("PruneOriginChanges: %v", err)
|
|
}
|
|
|
|
gotOld, err := db.GetOriginChanges(t.Context(), oldPrefix, 1000)
|
|
if err != nil {
|
|
t.Fatalf("GetOriginChanges: %v", err)
|
|
}
|
|
|
|
if len(gotOld) != 500 {
|
|
t.Errorf("GetOriginChanges(oldPrefix) = %d rows, want 500", len(gotOld))
|
|
}
|
|
|
|
gotNew, err := db.GetOriginChanges(t.Context(), newPrefix, 1000)
|
|
if err != nil {
|
|
t.Fatalf("GetOriginChanges: %v", err)
|
|
}
|
|
|
|
if len(gotNew) != 600 {
|
|
t.Errorf("GetOriginChanges(newPrefix) = %d rows, want 600", len(gotNew))
|
|
}
|
|
|
|
gotOther, err := db.GetOriginChanges(t.Context(), other, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetOriginChanges: %v", err)
|
|
}
|
|
|
|
if len(gotOther) != 1 {
|
|
t.Errorf("GetOriginChanges(other) = %d rows, want 1", len(gotOther))
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesSurviveRouteDeletion checks that deleting a routes row
|
|
// leaves the prefix's origin_changes rows.
|
|
func TestOriginChangesSurviveRouteDeletion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := newRoutesTestDatabase(t)
|
|
prefix := netip.MustParsePrefix(tablePrefixV4)
|
|
|
|
err := db.WriteOriginChanges([]routetable.OriginChange{{
|
|
Prefix: prefix, OriginASN: asA, Announced: true, PeerCount: 1,
|
|
Time: tableTime(10),
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("WriteOriginChanges: %v", err)
|
|
}
|
|
|
|
// Write a route, then delete it.
|
|
table := routetable.New(routetable.Params{})
|
|
announceTo(table, tablePrefixV4, asA, peerA, 10)
|
|
writeTableChanges(t, db, table)
|
|
|
|
table.Withdraw(prefix, peerA)
|
|
writeTableChanges(t, db, table)
|
|
|
|
got, err := db.GetOriginChanges(t.Context(), prefix, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetOriginChanges: %v", err)
|
|
}
|
|
|
|
if len(got) != 1 {
|
|
t.Errorf("GetOriginChanges = %d rows, want 1", len(got))
|
|
}
|
|
}
|