check / check (push) Failing after 15m27s
Stop now counts the origin changes inside a running write, in a new writingOriginChanges field, and adds them to the count it logs. The prune test checks which rows survive, including a prefix with rows on both sides of 72 hours. The prefix page tests cover 5,000 and 5,001 rows and a row's time and peer count. New tests cover the dropped-rows warning and the dropped count's reset. The prune floor is named minOriginChangesPerPrefix. The README, TODO, package doc and doc comments are corrected to match. Model: deepseek-v4-pro
306 lines
8.5 KiB
Go
306 lines
8.5 KiB
Go
package routetable_test
|
|
|
|
import (
|
|
"math"
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bits-and-blooms/bitset"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/routetable"
|
|
)
|
|
|
|
// originChange returns the origin change the table records for prefix and asn
|
|
// at the clock's time, announced or withdrawn, with peerCount peers.
|
|
func originChange(
|
|
prefix netip.Prefix, asn int, announced bool, peerCount int, at time.Time,
|
|
) routetable.OriginChange {
|
|
return routetable.OriginChange{
|
|
Prefix: prefix,
|
|
OriginASN: asn,
|
|
Announced: announced,
|
|
PeerCount: peerCount,
|
|
Time: at,
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesRecordAnnouncedAndWithdrawn checks that a prefix and origin
|
|
// AS pair records an announced row when it gains its first peer, nothing for a
|
|
// second peer, and a withdrawn row when it loses its last peer.
|
|
func TestOriginChangesRecordAnnouncedAndWithdrawn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asA, routeOf(peerB, asA, 20))
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, true, 1, now),
|
|
})
|
|
|
|
table.Withdraw(prefix, peerA)
|
|
table.Withdraw(prefix, peerB)
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, false, 0, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesRecordAPeerMovingOrigin checks that a peer moving from one
|
|
// origin AS to another records a withdrawn row for the old one and an
|
|
// announced row for the new one.
|
|
func TestOriginChangesRecordAPeerMovingOrigin(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerA, asB, 20))
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, true, 1, now),
|
|
originChange(prefix, asA, false, 0, now),
|
|
originChange(prefix, asB, true, 1, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesRecordPeersDown checks that PeersDown records a withdrawn
|
|
// row for each prefix and origin AS pair it empties.
|
|
func TestOriginChangesRecordPeersDown(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
v4 := netip.MustParsePrefix(prefixV4)
|
|
v6 := netip.MustParsePrefix(prefixV6)
|
|
|
|
table.Announce(v4, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(v6, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(v4, asB, routeOf(peerB, asB, 20))
|
|
|
|
table.PeersDown([]string{peerA})
|
|
|
|
// peerA carried v4 and v6 with asA; both pairs are emptied. peerB still
|
|
// carries v4 with asB, so that pair is not.
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(v4, asA, true, 1, now),
|
|
originChange(v6, asA, true, 1, now),
|
|
originChange(v4, asB, true, 2, now),
|
|
originChange(v4, asA, false, 1, now),
|
|
originChange(v6, asA, false, 0, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesLoadRecordsNothing checks that Load records no origin
|
|
// change.
|
|
func TestOriginChangesLoadRecordsNothing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{
|
|
PeerNumbers: map[string]uint{peerA: 0},
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
peers := new(bitset.BitSet)
|
|
peers.Set(0)
|
|
|
|
table.Load(routetable.Change{
|
|
Prefix: prefix,
|
|
OriginASN: asA,
|
|
Peers: peers,
|
|
Routes: []routetable.Route{routeOf(peerA, asA, 10)},
|
|
})
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != 0 || len(changes) != 0 {
|
|
t.Errorf("TakeOriginChanges = %+v, %d; want none", changes, dropped)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesTakeReturnsOldestFirst checks that TakeOriginChanges
|
|
// returns the changes oldest first and at most max.
|
|
func TestOriginChangesTakeReturnsOldestFirst(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerB, asB, 20))
|
|
table.Announce(prefix, asC, routeOf(peerC, asC, 30))
|
|
|
|
first, dropped := table.TakeOriginChanges(2)
|
|
if dropped != 0 {
|
|
t.Errorf("dropped = %d, want 0", dropped)
|
|
}
|
|
|
|
if len(first) != 2 || first[0].OriginASN != asA || first[1].OriginASN != asB {
|
|
t.Errorf("first TakeOriginChanges = %+v, want the first two", first)
|
|
}
|
|
|
|
second, _ := table.TakeOriginChanges(math.MaxInt)
|
|
if len(second) != 1 || second[0].OriginASN != asC {
|
|
t.Errorf("second TakeOriginChanges = %+v, want the third", second)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesPutBackPutsRowsInFront checks that PutBackOriginChanges
|
|
// puts rows back in front, so the next TakeOriginChanges returns them first.
|
|
func TestOriginChangesPutBackPutsRowsInFront(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerB, asB, 20))
|
|
|
|
taken, _ := table.TakeOriginChanges(math.MaxInt)
|
|
table.Announce(prefix, asC, routeOf(peerC, asC, 30))
|
|
table.PutBackOriginChanges(taken)
|
|
|
|
changes, _ := table.TakeOriginChanges(math.MaxInt)
|
|
if len(changes) != 3 || changes[0].OriginASN != asA ||
|
|
changes[1].OriginASN != asB || changes[2].OriginASN != asC {
|
|
t.Errorf("TakeOriginChanges = %+v, want the put-back rows first", changes)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesDropPastTheLimit checks that a change past 100,000 waiting
|
|
// rows is dropped and counted.
|
|
func TestOriginChangesDropPastTheLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const limit = 100000
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
for range limit + 1 {
|
|
prefix := netip.PrefixFrom(addr, 32)
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
|
|
addr = addr.Next()
|
|
}
|
|
|
|
changes, dropped := table.TakeOriginChanges(limit)
|
|
if dropped != 1 {
|
|
t.Errorf("dropped = %d, want 1", dropped)
|
|
}
|
|
|
|
if len(changes) != limit {
|
|
t.Errorf("len(changes) = %d, want %d", len(changes), limit)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesTakeResetsTheDroppedCount checks that a TakeOriginChanges
|
|
// that reports dropped rows leaves the next one reporting none, so each drop
|
|
// is logged once, not on every later write.
|
|
func TestOriginChangesTakeResetsTheDroppedCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const limit = 100000
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
for range limit + 1 {
|
|
table.Announce(netip.PrefixFrom(addr, 32), asA, routeOf(peerA, asA, 10))
|
|
addr = addr.Next()
|
|
}
|
|
|
|
_, dropped := table.TakeOriginChanges(limit)
|
|
if dropped != 1 {
|
|
t.Fatalf("dropped = %d, want 1", dropped)
|
|
}
|
|
|
|
_, dropped = table.TakeOriginChanges(0)
|
|
if dropped != 0 {
|
|
t.Errorf("the next take's dropped = %d, want 0", dropped)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesPutBackTrimsToTheLimit checks that putting back what a
|
|
// failed write took, after new changes filled the table again, drops the
|
|
// oldest changes past the cap and counts them, so the cap still holds. The
|
|
// clock advances one second per change, so a change's time says its age.
|
|
func TestOriginChangesPutBackTrimsToTheLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
limit = 100000
|
|
taken = 20000
|
|
added = 20000
|
|
)
|
|
|
|
now := time.Unix(0, 0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time {
|
|
now = now.Add(time.Second)
|
|
|
|
return now
|
|
},
|
|
})
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
announce := func(n int) {
|
|
for range n {
|
|
table.Announce(netip.PrefixFrom(addr, 32), asA, routeOf(peerA, asA, 10))
|
|
addr = addr.Next()
|
|
}
|
|
}
|
|
|
|
announce(limit)
|
|
|
|
takenChanges, _ := table.TakeOriginChanges(taken)
|
|
|
|
announce(added)
|
|
|
|
table.PutBackOriginChanges(takenChanges)
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != added {
|
|
t.Errorf("dropped = %d, want %d", dropped, added)
|
|
}
|
|
|
|
if len(changes) != limit {
|
|
t.Errorf("len(changes) = %d, want %d", len(changes), limit)
|
|
}
|
|
|
|
// The put-back rows are the oldest, so they are the ones dropped. The
|
|
// first remaining change is the one that followed them.
|
|
if want := time.Unix(int64(added+1), 0); !changes[0].Time.Equal(want) {
|
|
t.Errorf("first change's time = %v, want %v", changes[0].Time, want)
|
|
}
|
|
}
|
|
|
|
// checkOriginChanges checks that TakeOriginChanges, taking every change,
|
|
// returns want.
|
|
func checkOriginChanges(
|
|
t *testing.T, table *routetable.Table, want []routetable.OriginChange,
|
|
) {
|
|
t.Helper()
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != 0 {
|
|
t.Errorf("dropped = %d, want 0", dropped)
|
|
}
|
|
|
|
if !reflect.DeepEqual(changes, want) {
|
|
t.Errorf("TakeOriginChanges = %+v, want %+v", changes, want)
|
|
}
|
|
}
|