Sample the prefix-length pages with bounded lookups, each prefix once (closes #66)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
/prefixlength/{length} and /prefixlength6/{length} grouped every live route
of that length by prefix, shuffled all the prefixes and joined the routes
back, so a request grew with the number of routes and listed a prefix once
for each peer that shared its newest time.
Now a length with at most 5000 prefixes has them all read and shuffled, and
the page lists 500 of them. A longer one is sampled with random row-id
lookups on the mask length index, at most four per prefix asked for, filled
up from the shuffled prefixes if the lookups keep repeating. Each prefix is
shown once, with its newest route.
Model: opus-5-5
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -45,6 +46,19 @@ const (
|
||||
asnSecondBand = 100
|
||||
)
|
||||
|
||||
// Parameters for the prefix-length sample tests.
|
||||
const (
|
||||
// sampleLimit is how many prefixes the tests ask the sample for.
|
||||
sampleLimit = 10
|
||||
// sampledPrefixCount is how many prefixes the sampling test stores, more
|
||||
// than sampleLimit so the sample has to choose among them.
|
||||
sampledPrefixCount = 30
|
||||
// secondV4PeerIP and secondV6PeerIP are the second peer each prefix is
|
||||
// learned from, besides the one mkV4Route and mkV6Route set.
|
||||
secondV4PeerIP = "192.0.2.2"
|
||||
secondV6PeerIP = "2001:db8::2"
|
||||
)
|
||||
|
||||
// TestGetIPInfoFindsMostSpecificLiveRoute stores nested live prefixes for both
|
||||
// families and checks that a lookup returns the most specific one covering the
|
||||
// address, ErrNoRoute when none covers it, and the next less specific prefix
|
||||
@@ -317,3 +331,141 @@ func TestBatchWriteDuringCheckpoint(t *testing.T) {
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestGetRandomPrefixesByLengthListsPrefixOnce stores one prefix of each
|
||||
// family learned from two peers in the same second and checks that the
|
||||
// prefix-length sample lists each of them once.
|
||||
func TestGetRandomPrefixesByLengthListsPrefixOnce(t *testing.T) {
|
||||
cfg := &config.Config{StateDir: t.TempDir()}
|
||||
|
||||
db, err := New(cfg, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
v4First := mkV4Route(t, "198.51.100.0/24", 64500, ts)
|
||||
v4Second := mkV4Route(t, "198.51.100.0/24", 64500, ts)
|
||||
v4Second.PeerIP = secondV4PeerIP
|
||||
v6First := mkV6Route("2001:db8::/32", 64500, ts)
|
||||
v6Second := mkV6Route("2001:db8::/32", 64500, ts)
|
||||
v6Second.PeerIP = secondV6PeerIP
|
||||
if err := db.UpsertLiveRouteBatch([]*LiveRoute{v4First, v4Second, v6First, v6Second}); err != nil {
|
||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||
}
|
||||
|
||||
for _, want := range []*LiveRoute{v4First, v6First} {
|
||||
routes, err := db.GetRandomPrefixesByLength(want.MaskLength, want.IPVersion, sampleLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRandomPrefixesByLength(%d, %d): %v", want.MaskLength, want.IPVersion, err)
|
||||
}
|
||||
if len(routes) != 1 || routes[0].Prefix != want.Prefix {
|
||||
t.Errorf("GetRandomPrefixesByLength(%d, %d) = %+v, want %s listed once",
|
||||
want.MaskLength, want.IPVersion, routes, want.Prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetRandomPrefixesByLengthListsNewestRoute stores a prefix learned from
|
||||
// two peers at different times, the older route first, and checks that the
|
||||
// prefix-length sample lists the newer route.
|
||||
func TestGetRandomPrefixesByLengthListsNewestRoute(t *testing.T) {
|
||||
cfg := &config.Config{StateDir: t.TempDir()}
|
||||
|
||||
db, err := New(cfg, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
older := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
olderRoute := mkV4Route(t, "198.51.100.0/24", 64500, older)
|
||||
newerRoute := mkV4Route(t, "198.51.100.0/24", 64500, older.Add(time.Minute))
|
||||
newerRoute.PeerIP = secondV4PeerIP
|
||||
if err := db.UpsertLiveRouteBatch([]*LiveRoute{olderRoute, newerRoute}); err != nil {
|
||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||
}
|
||||
|
||||
routes, err := db.GetRandomPrefixesByLength(newerRoute.MaskLength, ipVersionV4, sampleLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRandomPrefixesByLength: %v", err)
|
||||
}
|
||||
if len(routes) != 1 || routes[0].PeerIP != newerRoute.PeerIP {
|
||||
t.Errorf("GetRandomPrefixesByLength = %+v, want only the newer route, from %s",
|
||||
routes, newerRoute.PeerIP)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetRandomPrefixesByLengthSamplesManyPrefixes stores more /24 prefixes
|
||||
// than the sample asks for, each followed by a /16 so that routes of another
|
||||
// mask length sit between the /24 rows, and each prefix learned from two peers
|
||||
// in the same second. It checks both the sample the pages get, picked from the
|
||||
// shuffled prefixes, and the random lookups used for a mask length with more
|
||||
// than maxPrefixesToShuffle prefixes.
|
||||
func TestGetRandomPrefixesByLengthSamplesManyPrefixes(t *testing.T) {
|
||||
cfg := &config.Config{StateDir: t.TempDir()}
|
||||
|
||||
db, err := New(cfg, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
var routes []*LiveRoute
|
||||
var stored []string
|
||||
for i := range sampledPrefixCount {
|
||||
prefix := fmt.Sprintf("10.0.%d.0/24", i)
|
||||
for _, p := range []string{prefix, fmt.Sprintf("172.%d.0.0/16", i)} {
|
||||
first := mkV4Route(t, p, 64500, ts)
|
||||
second := mkV4Route(t, p, 64500, ts)
|
||||
second.PeerIP = secondV4PeerIP
|
||||
routes = append(routes, first, second)
|
||||
}
|
||||
stored = append(stored, prefix)
|
||||
}
|
||||
if err := db.UpsertLiveRouteBatch(routes); err != nil {
|
||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||
}
|
||||
slices.Sort(stored)
|
||||
|
||||
maskLength := routes[0].MaskLength
|
||||
sample, err := db.GetRandomPrefixesByLength(maskLength, ipVersionV4, sampleLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRandomPrefixesByLength: %v", err)
|
||||
}
|
||||
var listed []string
|
||||
for _, route := range sample {
|
||||
listed = append(listed, route.Prefix)
|
||||
}
|
||||
checkPrefixSample(t, "GetRandomPrefixesByLength", listed, stored)
|
||||
|
||||
looked, err := db.samplePrefixesByLength(
|
||||
context.Background(), "live_routes_v4", maskLength, sampleLimit, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("samplePrefixesByLength: %v", err)
|
||||
}
|
||||
checkPrefixSample(t, "samplePrefixesByLength", looked, stored)
|
||||
}
|
||||
|
||||
// checkPrefixSample checks that sample lists sampleLimit prefixes, each once
|
||||
// and each one of stored, and that it is not just the first sampleLimit of
|
||||
// stored, which is in text order.
|
||||
func checkPrefixSample(t *testing.T, name string, sample, stored []string) {
|
||||
t.Helper()
|
||||
|
||||
if len(sample) != sampleLimit {
|
||||
t.Errorf("%s listed %d prefixes, want %d", name, len(sample), sampleLimit)
|
||||
}
|
||||
listed := make(map[string]bool)
|
||||
for _, prefix := range sample {
|
||||
if listed[prefix] || !slices.Contains(stored, prefix) {
|
||||
t.Errorf("%s lists %s twice or it is not one of the /24s", name, prefix)
|
||||
}
|
||||
listed[prefix] = true
|
||||
}
|
||||
if slices.Equal(slices.Sorted(slices.Values(sample)), stored[:sampleLimit]) {
|
||||
t.Errorf("%s lists just the first %d prefixes in text order", name, sampleLimit)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user