Read every route from the routing table in memory (part of #122)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The IP lookups, the AS and prefix pages and their API answers, the prefix-length pages and the stats read routes only from the routing table the prefix handler keeps. fx reads the table at startup and gives it to both. AS names, WHOIS details and first-seen times still come from the database. The IP lookup names the origin AS most peers carry, with the time of the newest kept route. The prefix answers list peers whose routes are not kept without a path or time. The stats gain origins_without_kept_route. They no longer read route times from the per-peer tables, so the two helpers that did are gone. Model: opus-5-5
This commit was merged in pull request #194.
This commit is contained in:
@@ -1780,6 +1780,165 @@ func TestGetASDetailsOfASNotInDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetASReadsTheRecordOrTheBuiltInList asks GetAS for an AS the asns table
|
||||
// holds, one only the AS info lookup holds, and one neither holds. The first
|
||||
// gets its record's seen times, the second its handle and description from the
|
||||
// lookup, and only the third is not known.
|
||||
func TestGetASReadsTheRecordOrTheBuiltInList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
const (
|
||||
storedASN = 64500 // a documentation AS, not in the lookup
|
||||
listedASN = 15169 // in the lookup, not stored
|
||||
unknownASN = 4200000001 // in neither
|
||||
)
|
||||
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
|
||||
_, err = db.GetOrCreateASN(storedASN, ts)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateASN: %v", err)
|
||||
}
|
||||
|
||||
listed, ok := asinfo.Get(listedASN)
|
||||
if !ok {
|
||||
t.Fatalf("AS%d is not in the AS info data", listedASN)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
asn int
|
||||
want ASN
|
||||
wantKnown bool
|
||||
}{
|
||||
{storedASN, ASN{ASN: storedASN, FirstSeen: ts, LastSeen: ts}, true},
|
||||
{listedASN, ASN{
|
||||
ASN: listedASN, Handle: listed.Handle, Description: listed.Description,
|
||||
}, true},
|
||||
{unknownASN, ASN{ASN: unknownASN}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, known, err := db.GetAS(t.Context(), tt.asn)
|
||||
if err != nil {
|
||||
t.Errorf("GetAS(%d): %v", tt.asn, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !got.FirstSeen.Equal(tt.want.FirstSeen) ||
|
||||
!got.LastSeen.Equal(tt.want.LastSeen) {
|
||||
t.Errorf("GetAS(%d) seen %v to %v, want %v to %v", tt.asn,
|
||||
got.FirstSeen, got.LastSeen, tt.want.FirstSeen, tt.want.LastSeen)
|
||||
}
|
||||
|
||||
got.FirstSeen, got.LastSeen = tt.want.FirstSeen, tt.want.LastSeen
|
||||
if *got != tt.want || known != tt.wantKnown {
|
||||
t.Errorf("GetAS(%d) = %+v, known %t; want %+v, known %t",
|
||||
tt.asn, *got, known, tt.want, tt.wantKnown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetASWHOISReadsTheStoredDetails stores an AS with WHOIS details and asks
|
||||
// GetASWHOIS for it and for an AS the asns table does not hold. The first gets
|
||||
// its stored handle, description and details, and the time they were fetched.
|
||||
// The second gets only its number, although the AS info lookup holds it.
|
||||
func TestGetASWHOISReadsTheStoredDetails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
const storedASN, unstoredASN = 15169, 13335
|
||||
|
||||
_, err = db.GetOrCreateASN(storedASN, time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC))
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateASN: %v", err)
|
||||
}
|
||||
|
||||
update := &ASNWHOISUpdate{
|
||||
ASN: storedASN, ASName: "TEST-AS", OrgName: "Test Org", OrgID: "ORG-1",
|
||||
Address: "1 Test Street", CountryCode: "DE", AbuseEmail: "abuse@test",
|
||||
RIR: "RIPE",
|
||||
}
|
||||
|
||||
err = db.UpdateASNWHOIS(t.Context(), update)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateASNWHOIS: %v", err)
|
||||
}
|
||||
|
||||
got, err := db.GetASWHOIS(t.Context(), storedASN)
|
||||
if err != nil {
|
||||
t.Fatalf("GetASWHOIS(%d): %v", storedASN, err)
|
||||
}
|
||||
|
||||
want := ASN{
|
||||
ASN: storedASN, Handle: asinfo.GetHandle(storedASN),
|
||||
Description: asinfo.GetDescription(storedASN), ASName: update.ASName,
|
||||
OrgName: update.OrgName, OrgID: update.OrgID, Address: update.Address,
|
||||
CountryCode: update.CountryCode, AbuseEmail: update.AbuseEmail,
|
||||
RIR: update.RIR,
|
||||
}
|
||||
|
||||
if got.WHOISUpdatedAt == nil {
|
||||
t.Errorf("GetASWHOIS(%d) has no time the details were fetched", storedASN)
|
||||
}
|
||||
|
||||
got.WHOISUpdatedAt = nil
|
||||
if *got != want {
|
||||
t.Errorf("GetASWHOIS(%d) = %+v, want %+v", storedASN, *got, want)
|
||||
}
|
||||
|
||||
got, err = db.GetASWHOIS(t.Context(), unstoredASN)
|
||||
if err != nil || *got != (ASN{ASN: unstoredASN}) {
|
||||
t.Errorf("GetASWHOIS(%d) = %+v, %v; want only the number",
|
||||
unstoredASN, got, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPrefixFirstSeenReadsThePrefixTables stores an IPv4 and an IPv6
|
||||
// prefix and expects GetPrefixFirstSeen to give each its first-seen time, and
|
||||
// the zero time for a prefix that is not stored.
|
||||
func TestGetPrefixFirstSeenReadsThePrefixTables(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := New(&config.Config{StateDir: t.TempDir()}, 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)
|
||||
stored := map[string]time.Time{
|
||||
"192.0.2.0/24": ts,
|
||||
testV6Prefix: ts.Add(time.Hour),
|
||||
}
|
||||
|
||||
err = db.UpdatePrefixesBatch(stored)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdatePrefixesBatch: %v", err)
|
||||
}
|
||||
|
||||
stored["198.51.100.0/24"] = time.Time{}
|
||||
|
||||
for prefix, want := range stored {
|
||||
got, err := db.GetPrefixFirstSeen(t.Context(), netip.MustParsePrefix(prefix))
|
||||
if err != nil || !got.Equal(want) {
|
||||
t.Errorf("GetPrefixFirstSeen(%s) = %v, %v; want %v",
|
||||
prefix, got, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestASPrefixQueriesUseCoveringIndexes checks the plan of each query that
|
||||
// lists the prefixes an AS originates: a search of a covering index and no
|
||||
// temporary B-tree, so the list reads one index entry per prefix, not one
|
||||
|
||||
Reference in New Issue
Block a user