check / check (push) Canceled after 0s
HandleMessage added one entry to the batch for every AS of every path, and waited for the write in progress once the batch held 300,000. In a burst of the feed its queue then filled, and the streamer dropped messages. The batch is now a map from AS number to the earliest and the latest time of its updates. It holds one entry per AS, so it no longer grows with the feed's rate. HandleMessage never waits for a write. asnBatchSize now counts ASes. Stop logs the entries of the batch being written and of the next batch under "batch_entries". An AS in both counts twice. Model: opus-5-5
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package routewatch
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
|
)
|
|
|
|
// pathOfASes returns an AS path of n different ASes: AS 1 to AS n. With n up to
|
|
// asnBatchSize, none of them is testASNA to testASND.
|
|
func pathOfASes(n int) ristypes.ASPath {
|
|
sequence := make([]int, n)
|
|
for i := range sequence {
|
|
sequence[i] = i + 1
|
|
}
|
|
|
|
return ristypes.ASPath{Sequence: sequence}
|
|
}
|
|
|
|
// TestASHandlerStoresTheEarliestAndLatestTimesOfANewAS handles two updates
|
|
// whose path carries an AS the database does not hold yet, in one batch into a
|
|
// real database. The second is 5 seconds after the first, and the handler gets
|
|
// it first. The AS is stored first seen at the time of the first update and
|
|
// last seen at the time of the second.
|
|
func TestASHandlerStoresTheEarliestAndLatestTimesOfANewAS(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := newTestDatabase(t)
|
|
|
|
// Built without NewASHandler's flush goroutine, so the test flushes itself.
|
|
h := &ASHandler{
|
|
db: db, logger: logger.New(), batch: make(map[int]database.SeenTimes),
|
|
}
|
|
|
|
first := peerAnnouncement(testPeerIP, testV4Prefix)
|
|
second := peerAnnouncement(testPeerIP, testV4Prefix)
|
|
second.ParsedTimestamp = first.ParsedTimestamp.Add(secondUpdateAfter)
|
|
|
|
h.HandleMessage(second)
|
|
h.HandleMessage(first)
|
|
h.flushBatch()
|
|
|
|
asn, _, err := db.GetAS(t.Context(), testASNA)
|
|
if err != nil {
|
|
t.Fatalf("GetAS(%d): %v", testASNA, err)
|
|
}
|
|
|
|
if !asn.FirstSeen.Equal(first.ParsedTimestamp) {
|
|
t.Errorf("first_seen of AS%d = %s, want %s", testASNA,
|
|
asn.FirstSeen, first.ParsedTimestamp)
|
|
}
|
|
|
|
if !asn.LastSeen.Equal(second.ParsedTimestamp) {
|
|
t.Errorf("last_seen of AS%d = %s, want %s", testASNA,
|
|
asn.LastSeen, second.ParsedTimestamp)
|
|
}
|
|
}
|
|
|
|
// TestASHandlerRecordsNothingFromAPathWithASZero handles an update whose AS
|
|
// path holds AS 0 between two other ASes. RFC 7607 makes such a path
|
|
// malformed, so neither AS is recorded.
|
|
func TestASHandlerRecordsNothingFromAPathWithASZero(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := newMockStore()
|
|
|
|
// Built without NewASHandler's flush goroutine, so the test flushes itself.
|
|
h := &ASHandler{
|
|
db: store, logger: logger.New(), batch: make(map[int]database.SeenTimes),
|
|
}
|
|
|
|
msg := peerAnnouncement(testPeerIP, testV4Prefix)
|
|
msg.Path = decodePath(t, testASNA, 0, testASNB)
|
|
h.HandleMessage(msg)
|
|
h.flushBatch()
|
|
|
|
if len(store.ASNs) > 0 {
|
|
t.Errorf("ASes recorded = %v, want none",
|
|
slices.Sorted(maps.Keys(store.ASNs)))
|
|
}
|
|
}
|